diff --git a/.github/workflows/web-deploy.yml b/.github/workflows/web-deploy.yml
index c29e7171..a2325899 100644
--- a/.github/workflows/web-deploy.yml
+++ b/.github/workflows/web-deploy.yml
@@ -2,7 +2,7 @@ name: deploy web to GitHub Pages
on:
push:
- branches: [ master, "wb/webui-actions-1" ]
+ branches: [ master ]
paths:
- 'web/**'
@@ -22,6 +22,7 @@ concurrency:
jobs:
build-landing-page:
+ name: Build landing page
runs-on: ubuntu-latest
steps:
- name: Checkout
@@ -32,6 +33,7 @@ jobs:
path: './web/public'
build-explorer:
+ name: Build capa explorer web
runs-on: ubuntu-latest
steps:
- name: Checkout
@@ -63,12 +65,51 @@ jobs:
name: explorer
path: './web/explorer/dist'
+ build-rules:
+ name: Build rules site
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check out the repository
+ uses: actions/checkout@v4
+ with:
+ submodules: 'recursive'
+ # full depth so that capa-rules has a full history
+ # and we can construct a timeline of rule updates.
+ fetch-depth: 0
+ - name: Set up Python
+ uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0
+ with:
+ python-version: '3.12'
+ - uses: extractions/setup-just@v2
+ - name: Install pagefind
+ uses: supplypike/setup-bin@v4
+ with:
+ uri: "https://github.com/CloudCannon/pagefind/releases/download/v1.1.0/pagefind-v1.1.0-x86_64-unknown-linux-musl.tar.gz"
+ name: "pagefind"
+ version: "1.1.0"
+ - name: Install dependencies
+ working-directory: ./web/rules
+ run: pip install -r requirements.txt
+ - name: Build the website
+ working-directory: ./web/rules
+ run: just build
+ - name: Index the website
+ working-directory: ./web/rules
+ run: pagefind --site "public"
+ # upload the build website to artifacts
+ # so that we can download and inspect, if desired.
+ - uses: actions/upload-artifact@v4
+ with:
+ name: rules
+ path: './web/rules/public'
+
deploy:
+ name: Deploy site to GitHub Pages
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
- needs: [build-landing-page, build-explorer]
+ needs: [build-landing-page, build-explorer, build-rules]
steps:
- uses: actions/download-artifact@v4
with:
@@ -78,6 +119,10 @@ jobs:
with:
name: explorer
path: './public/explorer'
+ - uses: actions/download-artifact@v4
+ with:
+ name: rules
+ path: './public/rules'
- name: Setup Pages
uses: actions/configure-pages@v4
- name: Upload artifact
diff --git a/.gitignore b/.gitignore
index 38d72570..ce07daf4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -126,3 +126,4 @@ Pipfile.lock
.github/binja/binaryninja
.github/binja/download_headless.py
.github/binja/BinaryNinja-headless.zip
+justfile
diff --git a/.justfile b/.justfile
new file mode 100644
index 00000000..90dc01e2
--- /dev/null
+++ b/.justfile
@@ -0,0 +1,20 @@
+@isort:
+ pre-commit run isort --show-diff-on-failure --all-files
+
+@black:
+ pre-commit run black --show-diff-on-failure --all-files
+
+@ruff:
+ pre-commit run ruff --all-files
+
+@flake8:
+ pre-commit run flake8 --hook-stage manual --all-files
+
+@mypy:
+ pre-commit run mypy --hook-stage manual --all-files
+
+@deptry:
+ pre-commit run deptry --hook-stage manual --all-files
+
+lint: isort black ruff flake8 mypy deptry
+
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 2d333f51..e0cfbc93 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -38,6 +38,7 @@ repos:
- "capa/"
- "scripts/"
- "tests/"
+ - "web/rules/scripts/"
always_run: true
pass_filenames: false
@@ -55,6 +56,7 @@ repos:
- "capa/"
- "scripts/"
- "tests/"
+ - "web/rules/scripts/"
always_run: true
pass_filenames: false
@@ -72,6 +74,7 @@ repos:
- "capa/"
- "scripts/"
- "tests/"
+ - "web/rules/scripts/"
always_run: true
pass_filenames: false
@@ -90,6 +93,7 @@ repos:
- "capa/"
- "scripts/"
- "tests/"
+ - "web/rules/scripts/"
always_run: true
pass_filenames: false
@@ -107,6 +111,7 @@ repos:
- "capa/"
- "scripts/"
- "tests/"
+ - "web/rules/scripts/"
always_run: true
pass_filenames: false
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 10f622a6..add50580 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,10 +6,47 @@ Unlock powerful malware analysis with capa's new [VMRay sandbox](https://www.vmr
### New Features
+- add landing page https://mandiant.github.io/capa/ @williballenthin #2310
+- add rules website https://mandiant.github.io/capa/rules @DeeyaSingh #2310
+- add .justfile @williballenthin #2325
+- dynamic: add support for VMRay dynamic sandbox traces #2208 @mike-hunhoff @r-sm2024 @mr-tz
+
+### Breaking Changes
+
+### New Rules (0)
+
+-
+
+### Bug Fixes
+
+- fix duplicate features shown in vverbose mode @williballenthin #2323
+
+### capa explorer IDA Pro plugin
+
+### Development
+
+### Raw diffs
+- [capa v7.2.0...master](https://github.com/mandiant/capa/compare/v7.2.0...master)
+- [capa-rules v7.2.0...master](https://github.com/mandiant/capa-rules/compare/v7.2.0...master)
+
+### v7.2.0
+capa v7.2.0 introduces a first version of capa explorer web: a web-based user interface to inspect capa results using your browser. Users can inspect capa result JSON documents in an online web instance or a standalone HTML page for offline usage. capa explorer supports interactive exploring of capa results to make it easier to understand them. Users can filter, sort, and see the details of all identified capabilities. capa explorer web was worked on by @s-ff as part of a [GSoC project](https://summerofcode.withgoogle.com/programs/2024/projects/cR3hjbsq), and it is available at https://mandiant.github.io/capa/explorer/#/.
+
+This release also adds a feature extractor for output from the DRAKVUF sandbox. Now, analysts can pass the resulting `drakmon.log` file to capa and extract capabilities from the artifacts captured by the sandbox. This feature extractor will also be added to the DRAKVUF sandbox as a post-processing script, and it was worked on by @yelhamer as part of a [GSoC project](https://summerofcode.withgoogle.com/programs/2024/projects/fCnBGuEC).
+
+Additionally, we fixed several bugs handling ELF files, and added the ability to filter capa analysis by functions or processes. We also added support to the IDA Pro extractor to leverage analyst recovered API names.
+
+Special thanks to our repeat and new contributors:
+* @lakshayletsgo for their first contribution in https://github.com/mandiant/capa/pull/2248
+* @msm-cert for their first contribution in https://github.com/mandiant/capa/pull/2143
+* @VascoSch92 for their first contribution in https://github.com/mandiant/capa/pull/2143
+
+### New Features
+
- webui: explore capa analysis results in a web-based UI online and offline #2224 @s-ff
- support analyzing DRAKVUF traces #2143 @yelhamer
-- dynamic: add support for VMRay dynamic sandbox traces #2208 @mike-hunhoff @r-sm2024 @mr-tz
- IDA extractor: extract names from dynamically resolved APIs stored in renamed global variables #2201 @Ana06
+- cli: add the ability to select which specific functions or processes to analyze @yelhamer
### Breaking Changes
@@ -20,7 +57,6 @@ Unlock powerful malware analysis with capa's new [VMRay sandbox](https://www.vmr
- communication/socket/attach-bpf-to-socket-on-linux jakub.jozwiak@mandiant.com
- anti-analysis/anti-av/overwrite-dll-text-section-to-remove-hooks jakub.jozwiak@mandiant.com
- nursery/delete-file-on-linux mehunhoff@google.com
--
### Bug Fixes
@@ -36,8 +72,8 @@ Unlock powerful malware analysis with capa's new [VMRay sandbox](https://www.vmr
- CI: update build.yml workflow to exclude web and documentation files #2270 @s-ff
### Raw diffs
-- [capa v7.1.0...master](https://github.com/mandiant/capa/compare/v7.1.0...master)
-- [capa-rules v7.1.0...master](https://github.com/mandiant/capa-rules/compare/v7.1.0...master)
+- [capa v7.1.0...7.2.0](https://github.com/mandiant/capa/compare/v7.1.0...7.2.0)
+- [capa-rules v7.1.0...7.2.0](https://github.com/mandiant/capa-rules/compare/v7.1.0...7.2.0)
## v7.1.0
The v7.1.0 release brings large performance improvements to capa's rule matching engine.
diff --git a/README.md b/README.md
index be07071c..3a987b7d 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,16 @@
-
+
+
+
+---
+
capa detects capabilities in executable files.
You run it against a PE, ELF, .NET module, shellcode file, or a sandbox report and it tells you what it thinks the program can do.
For example, it might suggest that the file is a backdoor, is capable of installing services, or relies on HTTP to communicate.
-To interactively inspect capa results in your browser use the [capa web explorer](https://mandiant.github.io/capa/explorer/).
+To interactively inspect capa results in your browser use the [capa explorer web](https://mandiant.github.io/capa/explorer/).
If you want to inspect or write capa rules, head on over to the [capa-rules repository](https://github.com/mandiant/capa-rules). Otherwise, keep reading.
@@ -72,12 +89,12 @@ Download stable releases of the standalone capa binaries [here](https://github.c
To use capa as a library or integrate with another tool, see [doc/installation.md](https://github.com/mandiant/capa/blob/master/doc/installation.md) for further setup instructions.
-# web explorer
-The [capa web explorer](https://mandiant.github.io/capa/explorer/) enables you to interactively explore capa results in your web browser. Besides the online version you can download a standalone HTML file for local offline usage.
+# capa explorer web
+The [capa explorer web](https://mandiant.github.io/capa/explorer/) enables you to interactively explore capa results in your web browser. Besides the online version you can download a standalone HTML file for local offline usage.
-
+
-More details on the web UI is available in the [capa web explorer README](https://github.com/mandiant/capa/blob/master/web/explorer/README.md).
+More details on the web UI is available in the [capa explorer web README](https://github.com/mandiant/capa/blob/master/web/explorer/README.md).
# example
diff --git a/capa/exceptions.py b/capa/exceptions.py
index 0c900d72..882c0718 100644
--- a/capa/exceptions.py
+++ b/capa/exceptions.py
@@ -23,3 +23,15 @@ class UnsupportedOSError(ValueError):
class EmptyReportError(ValueError):
pass
+
+
+class InvalidArgument(ValueError):
+ pass
+
+
+class NonExistantFunctionError(ValueError):
+ pass
+
+
+class NonExistantProcessError(ValueError):
+ pass
diff --git a/capa/features/extractors/base_extractor.py b/capa/features/extractors/base_extractor.py
index 002117fc..a58016bc 100644
--- a/capa/features/extractors/base_extractor.py
+++ b/capa/features/extractors/base_extractor.py
@@ -9,7 +9,9 @@
import abc
import hashlib
import dataclasses
-from typing import Any, Dict, Tuple, Union, Iterator
+from copy import copy
+from types import MethodType
+from typing import Any, Set, Dict, Tuple, Union, Iterator
from dataclasses import dataclass
# TODO(williballenthin): use typing.TypeAlias directly when Python 3.9 is deprecated
@@ -296,6 +298,22 @@ class StaticFeatureExtractor:
raise NotImplementedError()
+def FunctionFilter(extractor: StaticFeatureExtractor, functions: Set) -> StaticFeatureExtractor:
+ original_get_functions = extractor.get_functions
+
+ def filtered_get_functions(self):
+ yield from (f for f in original_get_functions() if f.address in functions)
+
+ # we make a copy of the original extractor object and then update its get_functions() method with the decorated filter one.
+ # this is in order to preserve the original extractor object's get_functions() method, in case it is used elsewhere in the code.
+ # an example where this is important is in our testfiles where we may use the same extractor object with different tests,
+ # with some of these tests needing to install a functions filter on the extractor object.
+ new_extractor = copy(extractor)
+ new_extractor.get_functions = MethodType(filtered_get_functions, extractor) # type: ignore
+
+ return new_extractor
+
+
@dataclass
class ProcessHandle:
"""
@@ -467,4 +485,20 @@ class DynamicFeatureExtractor:
raise NotImplementedError()
+def ProcessFilter(extractor: DynamicFeatureExtractor, processes: Set) -> DynamicFeatureExtractor:
+ original_get_processes = extractor.get_processes
+
+ def filtered_get_processes(self):
+ yield from (f for f in original_get_processes() if f.address.pid in processes)
+
+ # we make a copy of the original extractor object and then update its get_processes() method with the decorated filter one.
+ # this is in order to preserve the original extractor object's get_processes() method, in case it is used elsewhere in the code.
+ # an example where this is important is in our testfiles where we may use the same extractor object with different tests,
+ # with some of these tests needing to install a processes filter on the extractor object.
+ new_extractor = copy(extractor)
+ new_extractor.get_processes = MethodType(filtered_get_processes, extractor) # type: ignore
+
+ return new_extractor
+
+
FeatureExtractor: TypeAlias = Union[StaticFeatureExtractor, DynamicFeatureExtractor]
diff --git a/capa/main.py b/capa/main.py
index e81bf708..50afbb6c 100644
--- a/capa/main.py
+++ b/capa/main.py
@@ -17,7 +17,7 @@ import argparse
import textwrap
import contextlib
from types import TracebackType
-from typing import Any, Dict, List, Optional
+from typing import Any, Set, Dict, List, Optional, TypedDict
from pathlib import Path
import colorama
@@ -64,6 +64,7 @@ from capa.helpers import (
log_unsupported_drakvuf_report_error,
)
from capa.exceptions import (
+ InvalidArgument,
EmptyReportError,
UnsupportedOSError,
UnsupportedArchError,
@@ -86,9 +87,17 @@ from capa.features.common import (
FORMAT_FREEZE,
FORMAT_RESULT,
FORMAT_DRAKVUF,
+ STATIC_FORMATS,
+ DYNAMIC_FORMATS,
)
from capa.capabilities.common import find_capabilities, has_file_limitation, find_file_capabilities
-from capa.features.extractors.base_extractor import FeatureExtractor, StaticFeatureExtractor, DynamicFeatureExtractor
+from capa.features.extractors.base_extractor import (
+ ProcessFilter,
+ FunctionFilter,
+ FeatureExtractor,
+ StaticFeatureExtractor,
+ DynamicFeatureExtractor,
+)
RULES_PATH_DEFAULT_STRING = "(embedded rules)"
SIGNATURES_PATH_DEFAULT_STRING = "(embedded signatures)"
@@ -109,10 +118,17 @@ E_MISSING_CAPE_STATIC_ANALYSIS = 21
E_MISSING_CAPE_DYNAMIC_ANALYSIS = 22
E_EMPTY_REPORT = 23
E_UNSUPPORTED_GHIDRA_EXECUTION_MODE = 24
+E_INVALID_INPUT_FORMAT = 25
+E_INVALID_FEATURE_EXTRACTOR = 26
logger = logging.getLogger("capa")
+class FilterConfig(TypedDict, total=False):
+ processes: Set[int]
+ functions: Set[int]
+
+
@contextlib.contextmanager
def timing(msg: str):
t0 = time.time()
@@ -281,6 +297,22 @@ def install_common_args(parser, wanted=None):
help=f"select backend, {backend_help}",
)
+ if "restrict-to-functions" in wanted:
+ parser.add_argument(
+ "--restrict-to-functions",
+ type=lambda s: s.replace(" ", "").split(","),
+ default=[],
+ help="provide a list of comma-separated function virtual addresses to analyze (static analysis).",
+ )
+
+ if "restrict-to-processes" in wanted:
+ parser.add_argument(
+ "--restrict-to-processes",
+ type=lambda s: s.replace(" ", "").split(","),
+ default=[],
+ help="provide a list of comma-separated process IDs to analyze (dynamic analysis).",
+ )
+
if "os" in wanted:
oses = [
(OS_AUTO, "detect OS automatically - default"),
@@ -759,9 +791,10 @@ def get_extractor_from_cli(args, input_format: str, backend: str) -> FeatureExtr
os_ = get_os_from_cli(args, backend)
sample_path = get_sample_path_from_cli(args, backend)
+ extractor_filters = get_extractor_filters_from_cli(args, input_format)
try:
- return capa.loader.get_extractor(
+ extractor = capa.loader.get_extractor(
args.input_file,
input_format,
os_,
@@ -771,6 +804,7 @@ def get_extractor_from_cli(args, input_format: str, backend: str) -> FeatureExtr
disable_progress=args.quiet or args.debug,
sample_path=sample_path,
)
+ return apply_extractor_filters(extractor, extractor_filters)
except UnsupportedFormatError as e:
if input_format == FORMAT_CAPE:
log_unsupported_cape_report_error(str(e))
@@ -792,6 +826,38 @@ def get_extractor_from_cli(args, input_format: str, backend: str) -> FeatureExtr
raise ShouldExitError(E_CORRUPT_FILE) from e
+def get_extractor_filters_from_cli(args, input_format) -> FilterConfig:
+ if not hasattr(args, "restrict_to_processes") and not hasattr(args, "restrict_to_functions"):
+ # no processes or function filters were installed in the args
+ return {}
+
+ if input_format in STATIC_FORMATS:
+ if args.restrict_to_processes:
+ raise InvalidArgument("Cannot filter processes with static analysis.")
+ return {"functions": {int(addr, 0) for addr in args.restrict_to_functions}}
+ elif input_format in DYNAMIC_FORMATS:
+ if args.restrict_to_functions:
+ raise InvalidArgument("Cannot filter functions with dynamic analysis.")
+ return {"processes": {int(pid, 0) for pid in args.restrict_to_processes}}
+ else:
+ raise ShouldExitError(E_INVALID_INPUT_FORMAT)
+
+
+def apply_extractor_filters(extractor: FeatureExtractor, extractor_filters: FilterConfig):
+ if not any(extractor_filters.values()):
+ return extractor
+
+ # if the user specified extractor filters, then apply them here
+ if isinstance(extractor, StaticFeatureExtractor):
+ assert extractor_filters["functions"]
+ return FunctionFilter(extractor, extractor_filters["functions"])
+ elif isinstance(extractor, DynamicFeatureExtractor):
+ assert extractor_filters["processes"]
+ return ProcessFilter(extractor, extractor_filters["processes"])
+ else:
+ raise ShouldExitError(E_INVALID_FEATURE_EXTRACTOR)
+
+
def main(argv: Optional[List[str]] = None):
if sys.version_info < (3, 8):
raise UnsupportedRuntimeError("This version of capa can only be used with Python 3.8+")
@@ -806,6 +872,9 @@ def main(argv: Optional[List[str]] = None):
You can see the rule set here:
https://github.com/mandiant/capa-rules
+ You can load capa JSON output to capa explorer web:
+ https://github.com/mandiant/capa/explorer
+
To provide your own rule set, use the `-r` flag:
capa --rules /path/to/rules suspicious.exe
capa -r /path/to/rules suspicious.exe
@@ -831,7 +900,20 @@ def main(argv: Optional[List[str]] = None):
parser = argparse.ArgumentParser(
description=desc, epilog=epilog, formatter_class=argparse.RawDescriptionHelpFormatter
)
- install_common_args(parser, {"input_file", "format", "backend", "os", "signatures", "rules", "tag"})
+ install_common_args(
+ parser,
+ {
+ "input_file",
+ "format",
+ "backend",
+ "os",
+ "signatures",
+ "rules",
+ "tag",
+ "restrict-to-functions",
+ "restrict-to-processes",
+ },
+ )
parser.add_argument("-j", "--json", action="store_true", help="emit JSON instead of text")
args = parser.parse_args(args=argv)
diff --git a/capa/rules/__init__.py b/capa/rules/__init__.py
index 5f567ea1..5ecad5cc 100644
--- a/capa/rules/__init__.py
+++ b/capa/rules/__init__.py
@@ -575,6 +575,15 @@ def trim_dll_part(api: str) -> str:
return api
+def unique(sequence):
+ """deduplicate the items in the given sequence, returning a list with the same order.
+
+ via: https://stackoverflow.com/a/58666031
+ """
+ seen = set()
+ return [x for x in sequence if not (x in seen or seen.add(x))] # type: ignore [func-returns-value]
+
+
def build_statements(d, scopes: Scopes):
if len(d.keys()) > 2:
raise InvalidRule("too many statements")
@@ -582,21 +591,21 @@ def build_statements(d, scopes: Scopes):
key = list(d.keys())[0]
description = pop_statement_description_entry(d[key])
if key == "and":
- return ceng.And([build_statements(dd, scopes) for dd in d[key]], description=description)
+ return ceng.And(unique(build_statements(dd, scopes) for dd in d[key]), description=description)
elif key == "or":
- return ceng.Or([build_statements(dd, scopes) for dd in d[key]], description=description)
+ return ceng.Or(unique(build_statements(dd, scopes) for dd in d[key]), description=description)
elif key == "not":
if len(d[key]) != 1:
raise InvalidRule("not statement must have exactly one child statement")
return ceng.Not(build_statements(d[key][0], scopes), description=description)
elif key.endswith(" or more"):
count = int(key[: -len("or more")])
- return ceng.Some(count, [build_statements(dd, scopes) for dd in d[key]], description=description)
+ return ceng.Some(count, unique(build_statements(dd, scopes) for dd in d[key]), description=description)
elif key == "optional":
# `optional` is an alias for `0 or more`
# which is useful for documenting behaviors,
# like with `write file`, we might say that `WriteFile` is optionally found alongside `CreateFileA`.
- return ceng.Some(0, [build_statements(dd, scopes) for dd in d[key]], description=description)
+ return ceng.Some(0, unique(build_statements(dd, scopes) for dd in d[key]), description=description)
elif key == "process":
if Scope.FILE not in scopes:
@@ -672,7 +681,7 @@ def build_statements(d, scopes: Scopes):
# - arch: i386
# - mnemonic: cmp
#
- statements = ceng.And([build_statements(dd, Scopes(static=Scope.INSTRUCTION)) for dd in d[key]])
+ statements = ceng.And(unique(build_statements(dd, Scopes(static=Scope.INSTRUCTION)) for dd in d[key]))
return ceng.Subscope(Scope.INSTRUCTION, statements, description=description)
diff --git a/capa/version.py b/capa/version.py
index 65fe77ff..b12f2879 100644
--- a/capa/version.py
+++ b/capa/version.py
@@ -5,7 +5,7 @@
# 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.
-__version__ = "7.1.0"
+__version__ = "7.2.0"
def get_major_version():
diff --git a/doc/usage.md b/doc/usage.md
index 74b163f4..949e03e1 100644
--- a/doc/usage.md
+++ b/doc/usage.md
@@ -9,6 +9,22 @@ Use the `-t` option to run rules with the given metadata value (see the rule fie
For example, `capa -t william.ballenthin@mandiant.com` runs rules that reference Willi's email address (probably as the author), or
`capa -t communication` runs rules with the namespace `communication`.
+### only analyze selected functions
+Use the `--restrict-to-functions` option to extract capabilities from only a selected set of functions. This is useful for analyzing
+large functions and figuring out their capabilities and their address of occurance; for example: PEB access, RC4 encryption, etc.
+
+To use this, you can copy the virtual addresses from your favorite disassembler and pass them to capa as follows:
+`capa sample.exe --restrict-to-functions 0x4019C0,0x401CD0`. If you add the `-v` option then capa will extract the interesting parts of a function for you.
+
+### only analyze selected processes
+Use the `--restrict-to-processes` option to extract capabilities from only a selected set of processes. This is useful for filtering the noise
+generated from analyzing non-malicious processes that can be reported by some sandboxes, as well as reduce the execution time
+by not analyzing such processes in the first place.
+
+To use this, you can pick the PIDs of the processes you are interested in from the sandbox-generated process tree (or from the sandbox-reported malware PID)
+and pass that to capa as follows: `capa report.log --restrict-to-processes 3888,3214,4299`. If you add the `-v` option then capa will tell you
+which threads perform what actions (encrypt/decrypt data, initiate a connection, etc.).
+
### IDA Pro plugin: capa explorer
Please check out the [capa explorer documentation](/capa/ida/plugin/README.md).
@@ -16,4 +32,4 @@ Please check out the [capa explorer documentation](/capa/ida/plugin/README.md).
Set the environment variable `CAPA_SAVE_WORKSPACE` to instruct the underlying analysis engine to
cache its intermediate results to the file system. For example, vivisect will create `.viv` files.
Subsequently, capa may run faster when reprocessing the same input file.
-This is particularly useful during rule development as you repeatedly test a rule against a known sample.
\ No newline at end of file
+This is particularly useful during rule development as you repeatedly test a rule against a known sample.
diff --git a/pyproject.toml b/pyproject.toml
index c2e686b7..e7218a77 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -125,7 +125,7 @@ dev = [
"pytest-sugar==1.0.0",
"pytest-instafail==0.5.0",
"pytest-cov==5.0.0",
- "flake8==7.1.0",
+ "flake8==7.1.1",
"flake8-bugbear==24.4.26",
"flake8-encodings==0.5.1",
"flake8-comprehensions==3.15.0",
@@ -151,7 +151,7 @@ dev = [
"types-psutil==6.0.0.20240621",
"types_requests==2.32.0.20240712",
"types-protobuf==5.27.0.20240626",
- "deptry==0.17.0"
+ "deptry==0.19.1"
]
build = [
# Dev and build dependencies are not relaxed because
@@ -173,7 +173,8 @@ scripts = [
[tool.deptry]
extend_exclude = [
"sigs",
- "tests"
+ "tests",
+ "web",
]
# dependencies marked as first party, to inform deptry that they are local
diff --git a/requirements.txt b/requirements.txt
index 7bc74d07..5067f2e0 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -12,7 +12,7 @@ cxxfilt==0.2.2
dncil==1.0.2
dnfile==0.15.0
funcy==2.0
-humanize==4.9.0
+humanize==4.10.0
ida-netnode==3.0
ida-settings==2.1.0
intervaltree==3.1.0
diff --git a/scripts/capa2sarif.py b/scripts/capa2sarif.py
index 63e0c6e2..79330479 100644
--- a/scripts/capa2sarif.py
+++ b/scripts/capa2sarif.py
@@ -84,8 +84,7 @@ def main() -> int:
args = _parse_args()
try:
- with Path(args.capa_output).open() as capa_output:
- json_data = json.load(capa_output)
+ json_data = json.loads(Path(args.capa_output).read_text(encoding="utf-8"))
except ValueError:
logger.error("Input data was not valid JSON, input should be a capa json output file.")
return -1
diff --git a/tests/test_capabilities.py b/tests/test_capabilities.py
index ddc7f6c3..5c6de51b 100644
--- a/tests/test_capabilities.py
+++ b/tests/test_capabilities.py
@@ -9,6 +9,7 @@
import textwrap
import capa.capabilities.common
+from capa.features.extractors.base_extractor import FunctionFilter
def test_match_across_scopes_file_function(z9324d_extractor):
@@ -174,6 +175,37 @@ def test_subscope_bb_rules(z9324d_extractor):
assert "test rule" in capabilities
+def test_match_specific_functions(z9324d_extractor):
+ rules = capa.rules.RuleSet(
+ [
+ capa.rules.Rule.from_yaml(
+ textwrap.dedent(
+ """
+ rule:
+ meta:
+ name: receive data
+ scopes:
+ static: function
+ dynamic: call
+ examples:
+ - 9324d1a8ae37a36ae560c37448c9705a:0x401CD0
+ features:
+ - or:
+ - api: recv
+ """
+ )
+ )
+ ]
+ )
+ extractor = FunctionFilter(z9324d_extractor, {0x4019C0})
+ capabilities, meta = capa.capabilities.common.find_capabilities(rules, extractor)
+ matches = capabilities["receive data"]
+ # test that we received only one match
+ assert len(matches) == 1
+ # and that this match is from the specified function
+ assert matches[0][0] == 0x4019C0
+
+
def test_byte_matching(z9324d_extractor):
rules = capa.rules.RuleSet(
[
diff --git a/web/explorer/DEVELOPMENT.md b/web/explorer/DEVELOPMENT.md
index dbfb3025..e9b15709 100644
--- a/web/explorer/DEVELOPMENT.md
+++ b/web/explorer/DEVELOPMENT.md
@@ -1,6 +1,6 @@
-# Development Guide for Capa Explorer Web
+# Development Guide for capa explorer web
-This guide will help you set up the Capa Explorer Web project for local development.
+This guide will help you set up the capa explorer web project for local development.
## Prerequisites
@@ -31,7 +31,7 @@ Before you begin, ensure you have the following installed:
npm run dev
```
- This will start the Vite development server. The application should now be running at `http://localhost:`
+ This will start the Vite development server. The application should now be running at `http://localhost:`.
## Project Structure
@@ -80,7 +80,7 @@ Or, you can build a standalone bundle application that can be used offline:
npm run build:bundle
```
-This will generate an offline HTML bundle file in the `dist/` directory.
+This will generate an offline HTML bundle file in the `capa-explorer-web/` directory.
## Testing
@@ -98,12 +98,13 @@ We use ESLint for linting and Prettier for code formatting. Run the linter with:
```
npm run lint
+npm run format:check
npm run format
```
## Working with PrimeVue Components
-Capa Explorer Web uses the PrimeVue UI component library. When adding new features or modifying existing ones, refer to the [PrimeVue documentation](https://primevue.org/vite) for available components and their usage.
+capa explorer web uses the PrimeVue UI component library. When adding new features or modifying existing ones, refer to the [PrimeVue documentation](https://primevue.org/vite) for available components and their usage.
## Best Practices
diff --git a/web/explorer/README.md b/web/explorer/README.md
index 341e91a7..b616c181 100644
--- a/web/explorer/README.md
+++ b/web/explorer/README.md
@@ -1,6 +1,6 @@
-# Capa Explorer Web
+# capa explorer web
-Capa Explorer Web is a browser-based user interface for exploring program capabilities identified by capa. It provides an intuitive and interactive way to analyze and visualize the results of capa analysis.
+capa explorer web is a browser-based user interface for exploring program capabilities identified by capa. It provides an intuitive and interactive way to analyze and visualize the results of capa analysis.
## Features
@@ -11,31 +11,34 @@ Capa Explorer Web is a browser-based user interface for exploring program capabi
## Getting Started
-1. **Access the Application**: Open Capa Explorer Web in your web browser.
- You can start using Capa Explorer Web by accessing [https://mandiant.github.io/capa](https://mandiant.github.io/capa/) or running it locally by dowloading the offline release in the [releases](https://github.com/mandiant/capa/releases) section and loading it in your browser.
+1. **Access the application**: Open capa explorer web in your web browser.
+ You can start using capa explorer web by accessing [https://mandiant.github.io/capa](https://mandiant.github.io/capa/explorer) or running it locally by downloading the offline release from the top right-hand corner and opening it in your web browser.
-2. **Import capa Results**:
+2. **Import capa results**:
- Click on "Upload from local" to select a capa analysis document file from your computer (with a version higher than 7.0.0).
+ - You can generate the analysis document by running `capa.exe -j results.json sample.exe_`
- Or, paste a URL to a capa JSON file and click the arrow button to load it.
+ - Like for the other import mechanisms, loading of both plain (`.json`) and GZIP compressed JSON (`.json.gz`) files is supported).
- Alternatively, use the "Preview Static" or "Preview Dynamic" for sample data.
-3. **Explore the Results**:
+3. **Explore the results**:
- Use the tree view to navigate through the identified capabilities.
- Toggle between different views using the checkboxes in the settings panel:
- "Show capabilities by function/process" for grouped analysis.
- - "Show library rule matches" to include or exclude library rules.
+ - "Show distinct library rule matches" to include or exclude library rules.
+ - "Show columns filters" to show per-column search filters.
-4. **Interact with the Data**:
- - Expand/collapse nodes in the table to see more details.
+4. **Interact with the results**:
+ - Expand/collapse nodes in the table to see more details by clicking rows or clicking arrow icons.
- Use the search and filter options to find specific features, functions or capabilities (rules).
- - Right click on rule names to view their source code or additional information.
+ - Right click on rule names (and `match` nodes) to view their source code or additional information.
## Feedback and Contributions
-We welcome your feedback and contributions to improve the web-based Capa Explorer. Please report any issues or suggest enhancements through the `capa` GitHub repository.
+We welcome your feedback and contributions to improve the web-based capa explorer. Please report any issues or suggest enhancements through the `capa` GitHub repository.
---
-For developers interested in building or contributing to Capa Explorer WebUI, please refer to our [Development Guide](DEVELOPMENT.md).
+For developers interested in building or contributing to capa explorer web, please refer to our [Development Guide](DEVELOPMENT.md).
diff --git a/web/explorer/index.html b/web/explorer/index.html
index d72238ac..3330f702 100644
--- a/web/explorer/index.html
+++ b/web/explorer/index.html
@@ -4,7 +4,8 @@
- Capa Explorer
+ capa explorer web
+
diff --git a/web/explorer/src/components/DescriptionPanel.vue b/web/explorer/src/components/DescriptionPanel.vue
index 9dfd5db8..f7009d78 100644
--- a/web/explorer/src/components/DescriptionPanel.vue
+++ b/web/explorer/src/components/DescriptionPanel.vue
@@ -1,16 +1,46 @@
-
+
-
capa: identify program capabilities
+
capa: identify program capabilities
-
- Capa-WebUI is a web-based tool for exploring the capabilities identified in a program. It can be used to
- search and display the rule matches in different viewing modes.
+
+ capa explorer web is a web-based tool for exploring the capabilities identified in a program. It can be
+ used to search and display the rule matches in different viewing modes.
+
+
+
+
New to capa? Follow these quick steps to get started:
+ capa is the FLARE team's
+ free and open-source tool
+ to identify capabilities in executable files.
+
+
+ Triage unknown files, guide reverse engineering, and hunt across a corpus for novel malware.
+ Refer to capa's rule set as an encyclopedia of techniques used in real-world attacks,
+ pivoting across MITRE ATT&CK
+ and Malware Behavior Catalog (MBC)
+ references.
+
+ capa recognizes behaviors by matching rules crafted by expert reverse engineers.
+
+ Rules describe logical combinations of features familiar to human analysts.
+ Things like:
+
+
+
API calls, like CreateRemoteThread,
+
integer constants, like 0x100000001b3 = FNV prime,
+
string references, like "ZIG_DEBUG_COLOR".
+
+
+ capa looks for these features within instructions, basic blocks, and functions, having already disassembled the input file.
+ In contrast to YARA, which primarily searches for sequences of bytes, capa rules describe features at the code-level.
+
+ capa v7.2.0
+ introduces a first version of capa explorer web: a web-based user interface to inspect capa results using your browser.
+ capa explorer web was worked on by @s-ff as part of a GSoC project, and it is available at https://mandiant.github.io/capa/explorer/.
+ This release also adds a feature extractor for output from the DRAKVUF sandbox. Now, analysts can pass the resulting drakmon.log file to capa and extract capabilities from the artifacts captured by the sandbox.
+
+
+
New features:
+
+
webui: explore capa analysis results in a web-based UI online and offline
+
support analyzing DRAKVUF traces
+
IDA extractor: extract names from dynamically resolved APIs stored in renamed global variables
+
cli: add the ability to select which specific functions or processes to analyze
+
+
+
+
+
+
+
+
+
+
+
+ Integrates with Popular Reverse Engineering Tools
+
+
+
+ capa uses the results of static or dynamic analysis to find the capabilities of programs.
+ The tool can rely on many different analysis backends, including IDA, Ghidra, Binary Ninja, CAPE, DRAKVUF, and VMRay.
+ The capa Explorer plugins provide interfaces for understanding the behaviors in programs, directly within reverse engineering tools.
+
+
+
+
+
+
+
+ IDA Pro
+
+
+
+ Use the capa Explorer IDA Plugin to guide your reverse engineering, zeroing in on the interesting functions by behavior.
+
+
+
+
+
+ Ghidra
+
+
+
+ Invoke Ghidra in headless mode to collect features for capa, or use the capa Explorer Ghidra plugin to understand key functions.
+
+
+
+
+
+ Binary Ninja
+
+
+
+ Use Binary Ninja as the disassembler backend, relying on its state-of-the-art code analysis to recover capabilities.
+
+
+
+
+
+ CAPE
+
+
+
+ Analyze the API trace captured by CAPE as it detonates malware, summarizing the behaviors seen across thousands of function calls.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Download capa
+
+
+
+ We distribute capa as a standalone executable and as a Python library.
+
+
+
+
+
+
+ The standalone executable comes ready-to-go with 890+ embedded rules.
+ Use this when analyzing files from the terminal on Windows, Linux, and macOS.
+ There's a quickstart guide here (PDF).
+
+ capa is already included with malware analysis distributions like
+ FLARE-VM
+ and REmnux.
+
+
+
+
+
+
+ The Python library makes it easy to integrate capa into other projects,
+ like our plugins for IDA Pro and Ghidra.
+ Follow the
+ instructions here
+ ,
+ which starts with installation it from PyPI like this:
+
+
+
pip install flare-capa
+
+
+ Here's an example
+ of using capa as a library to process many files in parallel.
+
+
+
+
+
+
+
+
+
+
+ capa
+
+
+
+
+ The FLARE team's open-source tool to identify capabilities in executable files.
+