Compare commits

...

1744 Commits

Author SHA1 Message Date
Moritz
f11661f8f2 release v8.0.0 (#2529)
* update to v8.0.0

* update website v8.0.0
2024-12-09 14:15:46 +01:00
Capa Bot
518dc3381c Sync capa rules submodule 2024-12-09 10:30:27 +00:00
Willi Ballenthin
5c60adaf96 BinExport2: better handle weird Ghidra expressions
analogous to the inspect-binexport2 issues reported in #2528 and #2530,
but this fixes the feature extractor.
2024-12-09 11:27:11 +01:00
Willi Ballenthin
4ab8d75629 changelog 2024-12-09 11:27:11 +01:00
Willi Ballenthin
51d852d1b3 inspect-binexport: better handle MSRs exported by Ghidra
closes #2530
2024-12-09 11:27:11 +01:00
Willi Ballenthin
aa8e4603d1 inspect-binexport2: render aarch64 vector element sizes
closes #2528
2024-12-09 11:27:11 +01:00
Willi Ballenthin
6c61a91778 main: use two lines when warning about stack trace 2024-12-09 11:27:11 +01:00
Capa Bot
e633e34517 Sync capa rules submodule 2024-12-09 09:52:22 +00:00
Willi Ballenthin
9c72c9067b binexport2: better pruning of comma expressions with a single child 2024-12-06 07:19:39 +01:00
Willi Ballenthin
168435cf75 changelog 2024-12-06 07:19:39 +01:00
Willi Ballenthin
5fdf7e61e2 inspect-binexport2: better render ARM lsl/lsr and pruned expressions 2024-12-06 07:19:39 +01:00
Willi Ballenthin
95fc747e6f binexport2: prune operands more precisely 2024-12-06 07:19:39 +01:00
Willi Ballenthin
1f374e4986 binexport2: fix handling of incorrect thunks (#2526)
* binexport2: fix handling of incorrect thunks

closes #2524

* changelog
2024-12-05 14:36:09 +01:00
Harshit Wadhwani
28c0234339 Fix: Issue #2307 (#2439)
* fix #2307

---------

Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2024-12-05 09:53:15 +01:00
Moritz
f57f909e68 Merge pull request #2523 from xusheng6/master
binja: move the stack string detection to the function level
2024-12-04 17:43:16 +01:00
Xusheng
02c359f79f binja: move the stack string detection to the function level. Fix https://github.com/mandiant/capa/issues/2516 2024-12-04 17:00:22 +08:00
Xusheng
4448d612f1 binja: fix up the analysis for the al-khaser_x64.exe_ file. Fix https://github.com/mandiant/capa/issues/2507 2024-12-04 09:36:08 +01:00
Xusheng
d7cf8d1251 Revert "skip test where BN misses the function"
This reverts commit 9ad3f06e1d.
2024-12-04 09:36:08 +01:00
Moritz
d1f3e43325 vmray: record command line info (#2515)
* vmray: record command line info
2024-12-03 19:56:30 +01:00
Capa Bot
83a46265df Sync capa rules submodule 2024-12-03 16:26:33 +00:00
Moritz
0c64bd4985 Merge pull request #2521 from mandiant/fix/2466-cape-model
make Process model flexible and procmemory optional
2024-12-03 14:28:29 +01:00
Capa Bot
ed86e5fb1b Sync capa rules submodule 2024-12-03 13:12:36 +00:00
Moritz
e1c786466a Merge pull request #2518 from mandiant/bn/skip-test
skip test where BN misses the function
2024-12-03 14:05:24 +01:00
mr-tz
959a234f0e make Process model flexible and procmemory optional 2024-12-03 13:02:19 +00:00
Moritz
e57de2beb4 Merge pull request #2513 from mandiant/dependabot/pip/protobuf-5.29.0
build(deps): bump protobuf from 5.28.2 to 5.29.0
2024-12-03 13:33:59 +01:00
Moritz
9c9b3711c0 Merge pull request #2519 from mandiant/ci/pre-commit
upgrade pre-commit config
2024-12-03 13:32:54 +01:00
mr-tz
65e2dac4c4 upgrade pre-commit config 2024-12-03 12:09:38 +00:00
mr-tz
9ad3f06e1d skip test where BN misses the function 2024-12-03 11:09:38 +00:00
Capa Bot
201ec07b58 Sync capa-testfiles submodule 2024-12-03 08:34:05 +00:00
Capa Bot
c85be8dc72 Sync capa-testfiles submodule 2024-12-03 08:26:34 +00:00
Moritz
54952feb07 Merge pull request #2501 from xusheng6/binja_database_support
Binja database support
2024-12-02 17:32:24 +01:00
dependabot[bot]
379d6ef313 build(deps): bump protobuf from 5.28.2 to 5.29.0
Bumps [protobuf](https://github.com/protocolbuffers/protobuf) from 5.28.2 to 5.29.0.
- [Release notes](https://github.com/protocolbuffers/protobuf/releases)
- [Changelog](https://github.com/protocolbuffers/protobuf/blob/main/protobuf_release.bzl)
- [Commits](https://github.com/protocolbuffers/protobuf/compare/v5.28.2...v5.29.0)

---
updated-dependencies:
- dependency-name: protobuf
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-02 15:42:28 +00:00
Xusheng
28fcd10d2e Add a unit test for Binary Ninja database 2024-12-02 23:34:07 +08:00
Xusheng
a6481df6c4 Add support for loading and analyzing Binary Ninja database 2024-12-02 23:34:07 +08:00
Moritz
abe80842cb Merge pull request #2511 from xusheng6/fix_llil_access
binja: retrieve the LLIL instruction itself without requesting the entire IL function
2024-12-02 14:01:11 +01:00
Xusheng
b6763ac5fe binja: retrieve the LLIL instruction itself without requesting the entire IL function 2024-12-02 17:11:24 +08:00
Capa Bot
5a284de438 Sync capa rules submodule 2024-11-28 10:34:29 +00:00
Capa Bot
8cfccbcb44 Sync capa-testfiles submodule 2024-11-28 10:25:40 +00:00
Moritz
01772d0de0 Merge pull request #2510 from mandiant/release/web-v1.0.0
explorer web: add release v1.0.0
2024-11-27 14:07:59 +01:00
Capa Bot
f0042157ab 🤖 explorer web: add release capa-explorer-web-v1.0.0-6a2330c 2024-11-27 13:03:18 +00:00
Moritz
6a2330c11a Merge pull request #2508 from fariss/update-web-release-workflow
ci: explorer web: modify web-release to open a PR
2024-11-27 14:01:47 +01:00
fariss
02b5e11380 ci: pin Github Actions version in web-release.yml
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2024-11-27 14:00:30 +01:00
Soufiane Fariss
32c428b989 ci: explorer web: modify web-release to open a PR 2024-11-26 19:49:10 +01:00
dependabot[bot]
20909c1d95 build(deps): bump python-flirt from 0.8.10 to 0.9.2
Bumps [python-flirt](https://github.com/williballenthin/lancelot) from 0.8.10 to 0.9.2.
- [Release notes](https://github.com/williballenthin/lancelot/releases)
- [Commits](https://github.com/williballenthin/lancelot/compare/v0.8.10...v0.9.2)

---
updated-dependencies:
- dependency-name: python-flirt
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-26 09:53:26 +01:00
dependabot[bot]
035b4f6ae6 build(deps): bump pydantic from 2.9.2 to 2.10.1
Bumps [pydantic](https://github.com/pydantic/pydantic) from 2.9.2 to 2.10.1.
- [Release notes](https://github.com/pydantic/pydantic/releases)
- [Changelog](https://github.com/pydantic/pydantic/blob/main/HISTORY.md)
- [Commits](https://github.com/pydantic/pydantic/compare/v2.9.2...v2.10.1)

---
updated-dependencies:
- dependency-name: pydantic
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-26 09:53:14 +01:00
dependabot[bot]
cb002567c4 build(deps): bump deptry from 0.20.0 to 0.21.1
Bumps [deptry](https://github.com/fpgmaas/deptry) from 0.20.0 to 0.21.1.
- [Release notes](https://github.com/fpgmaas/deptry/releases)
- [Changelog](https://github.com/fpgmaas/deptry/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fpgmaas/deptry/compare/0.20.0...0.21.1)

---
updated-dependencies:
- dependency-name: deptry
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-26 09:52:23 +01:00
dependabot[bot]
46c513c0a9 build(deps): bump ruff from 0.7.1 to 0.8.0
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.7.1 to 0.8.0.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.7.1...0.8.0)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-26 09:51:25 +01:00
dependabot[bot]
0f0523d2ba build(deps): bump setuptools from 75.3.0 to 75.6.0
Bumps [setuptools](https://github.com/pypa/setuptools) from 75.3.0 to 75.6.0.
- [Release notes](https://github.com/pypa/setuptools/releases)
- [Changelog](https://github.com/pypa/setuptools/blob/main/NEWS.rst)
- [Commits](https://github.com/pypa/setuptools/compare/v75.3.0...v75.6.0)

---
updated-dependencies:
- dependency-name: setuptools
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-26 09:51:12 +01:00
Xusheng
688841fd3b binja: fix crash when the IL of certain functions are not available. #2249 2024-11-25 21:50:53 +01:00
Xusheng
2a6ba62379 binja: support analyzing x86/x86_64 shellcode with binja backend (#2489) 2024-11-25 21:50:53 +01:00
Xusheng
ca7580d417 Update Binary Ninja version to 4.2 (#2499) 2024-11-25 21:50:53 +01:00
Capa Bot
7c01712843 Sync capa rules submodule 2024-11-25 08:22:20 +00:00
Capa Bot
ef02e4fe83 Sync capa rules submodule 2024-11-19 16:42:55 +00:00
Moritz
d51074385b Merge pull request #2490 from mandiant/call-subscope
allow call as valid subscope for call scoped rules
2024-11-19 17:34:57 +01:00
Capa Bot
d9ea57d29d Sync capa rules submodule 2024-11-19 15:51:56 +00:00
Moritz
8b7ec049f4 Merge pull request #2495 from mandiant/dependabot/pip/pygithub-2.5.0
build(deps): bump pygithub from 2.4.0 to 2.5.0
2024-11-19 12:29:14 +01:00
Moritz
c05e01cc3a Merge pull request #2494 from mandiant/dependabot/pip/flake8-comprehensions-3.16.0
build(deps): bump flake8-comprehensions from 3.15.0 to 3.16.0
2024-11-19 12:28:57 +01:00
Moritz
11bb0c3fbd Merge pull request #2493 from mandiant/dependabot/pip/pip-24.3.1
build(deps): bump pip from 24.2 to 24.3.1
2024-11-19 12:28:34 +01:00
Moritz
93da346f32 Merge pull request #2492 from mandiant/dependabot/pip/flake8-bugbear-24.10.31
build(deps): bump flake8-bugbear from 24.8.19 to 24.10.31
2024-11-19 12:28:13 +01:00
dependabot[bot]
3a2056b701 build(deps): bump setuptools from 75.2.0 to 75.3.0 (#2485)
Bumps [setuptools](https://github.com/pypa/setuptools) from 75.2.0 to 75.3.0.
- [Release notes](https://github.com/pypa/setuptools/releases)
- [Changelog](https://github.com/pypa/setuptools/blob/main/NEWS.rst)
- [Commits](https://github.com/pypa/setuptools/compare/v75.2.0...v75.3.0)

---
updated-dependencies:
- dependency-name: setuptools
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2024-11-19 10:47:04 +01:00
dependabot[bot]
915f3b0511 build(deps): bump pygithub from 2.4.0 to 2.5.0
Bumps [pygithub](https://github.com/pygithub/pygithub) from 2.4.0 to 2.5.0.
- [Release notes](https://github.com/pygithub/pygithub/releases)
- [Changelog](https://github.com/PyGithub/PyGithub/blob/main/doc/changes.rst)
- [Commits](https://github.com/pygithub/pygithub/compare/v2.4.0...v2.5.0)

---
updated-dependencies:
- dependency-name: pygithub
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-18 14:44:53 +00:00
dependabot[bot]
cd61983e43 build(deps): bump flake8-comprehensions from 3.15.0 to 3.16.0
Bumps [flake8-comprehensions](https://github.com/adamchainz/flake8-comprehensions) from 3.15.0 to 3.16.0.
- [Changelog](https://github.com/adamchainz/flake8-comprehensions/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/adamchainz/flake8-comprehensions/compare/3.15.0...3.16.0)

---
updated-dependencies:
- dependency-name: flake8-comprehensions
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-18 14:44:49 +00:00
dependabot[bot]
9627f7e5c3 build(deps): bump pip from 24.2 to 24.3.1
Bumps [pip](https://github.com/pypa/pip) from 24.2 to 24.3.1.
- [Changelog](https://github.com/pypa/pip/blob/main/NEWS.rst)
- [Commits](https://github.com/pypa/pip/compare/24.2...24.3.1)

---
updated-dependencies:
- dependency-name: pip
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-18 14:44:44 +00:00
dependabot[bot]
3ebec9ec2b build(deps): bump flake8-bugbear from 24.8.19 to 24.10.31
Bumps [flake8-bugbear](https://github.com/PyCQA/flake8-bugbear) from 24.8.19 to 24.10.31.
- [Release notes](https://github.com/PyCQA/flake8-bugbear/releases)
- [Commits](https://github.com/PyCQA/flake8-bugbear/compare/24.8.19...24.10.31)

---
updated-dependencies:
- dependency-name: flake8-bugbear
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-18 14:44:40 +00:00
Capa Bot
295cd413bb Sync capa rules submodule 2024-11-15 10:12:32 +00:00
mr-tz
03e4778620 allow call as valid subscope for call scoped rules 2024-11-14 11:55:07 +00:00
dependabot[bot]
e8ad207245 build(deps): bump types-psutil from 6.0.0.20240901 to 6.1.0.20241102 (#2486)
Bumps [types-psutil](https://github.com/python/typeshed) from 6.0.0.20240901 to 6.1.0.20241102.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-psutil
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2024-11-12 12:37:25 +01:00
dependabot[bot]
a31bd2cd15 build(deps): bump pytest-cov from 5.0.0 to 6.0.0 (#2484)
Bumps [pytest-cov](https://github.com/pytest-dev/pytest-cov) from 5.0.0 to 6.0.0.
- [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-cov/compare/v5.0.0...v6.0.0)

---
updated-dependencies:
- dependency-name: pytest-cov
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2024-11-12 12:35:59 +01:00
dependabot[bot]
9118946ecb build(deps): bump pyinstaller from 6.10.0 to 6.11.1 (#2487)
Bumps [pyinstaller](https://github.com/pyinstaller/pyinstaller) from 6.10.0 to 6.11.1.
- [Release notes](https://github.com/pyinstaller/pyinstaller/releases)
- [Changelog](https://github.com/pyinstaller/pyinstaller/blob/develop/doc/CHANGES.rst)
- [Commits](https://github.com/pyinstaller/pyinstaller/compare/v6.10.0...v6.11.1)

---
updated-dependencies:
- dependency-name: pyinstaller
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2024-11-12 12:35:19 +01:00
dependabot[bot]
7b32706bd4 build(deps): bump psutil from 6.0.0 to 6.1.0 (#2478)
Bumps [psutil](https://github.com/giampaolo/psutil) from 6.0.0 to 6.1.0.
- [Changelog](https://github.com/giampaolo/psutil/blob/master/HISTORY.rst)
- [Commits](https://github.com/giampaolo/psutil/compare/release-6.0.0...release-6.1.0)

---
updated-dependencies:
- dependency-name: psutil
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2024-11-11 16:42:57 +01:00
Moritz
c632d594a6 Use macOS 13 (#2488)
* update to macos-13
2024-11-11 16:42:26 +01:00
Capa Bot
4398b8ac31 Sync capa-testfiles submodule 2024-11-11 15:21:56 +00:00
dependabot[bot]
ec697c01f9 build(deps): bump mypy from 1.12.1 to 1.13.0 (#2476) 2024-10-30 17:03:41 +01:00
dependabot[bot]
097ed73ccd build(deps): bump ruff from 0.6.4 to 0.7.1 (#2475) 2024-10-30 17:03:29 +01:00
Capa Bot
4e121ae24f Sync capa rules submodule 2024-10-30 15:19:51 +00:00
Capa Bot
322e7a934e Sync capa rules submodule 2024-10-28 10:24:05 +00:00
dependabot[bot]
7d983af907 build(deps): bump xmltodict from 0.13.0 to 0.14.2 (#2470)
Bumps [xmltodict](https://github.com/martinblech/xmltodict) from 0.13.0 to 0.14.2.
- [Changelog](https://github.com/martinblech/xmltodict/blob/master/CHANGELOG.md)
- [Commits](https://github.com/martinblech/xmltodict/compare/v0.13.0...v0.14.2)

---
updated-dependencies:
- dependency-name: xmltodict
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2024-10-23 09:07:05 +02:00
dependabot[bot]
77758e8922 build(deps): bump mypy from 1.11.2 to 1.12.1 (#2469)
Bumps [mypy](https://github.com/python/mypy) from 1.11.2 to 1.12.1.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.11.2...v1.12.1)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2024-10-23 09:06:28 +02:00
dependabot[bot]
296255f581 build(deps): bump setuptools from 75.1.0 to 75.2.0 (#2468)
Bumps [setuptools](https://github.com/pypa/setuptools) from 75.1.0 to 75.2.0.
- [Release notes](https://github.com/pypa/setuptools/releases)
- [Changelog](https://github.com/pypa/setuptools/blob/main/NEWS.rst)
- [Commits](https://github.com/pypa/setuptools/compare/v75.1.0...v75.2.0)

---
updated-dependencies:
- dependency-name: setuptools
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2024-10-23 09:05:50 +02:00
dependabot[bot]
0237059cbd build(deps): bump black from 24.8.0 to 24.10.0 (#2462)
* build(deps): bump black from 24.8.0 to 24.10.0

Bumps [black](https://github.com/psf/black) from 24.8.0 to 24.10.0.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/24.8.0...24.10.0)

---
updated-dependencies:
- dependency-name: black
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-23 09:04:34 +02:00
Moritz
3241ee599f Merge pull request #2472 from s-ff/skip-changelog-for-dependabot
ci: skip changelog.yml when PR author is `dependabot`
2024-10-23 08:59:05 +02:00
Soufiane Fariss
24236dda0e ci: skip changelog.yml when PR author is dependabot 2024-10-23 00:05:52 +02:00
dependabot[bot]
d4d856767d build(deps): bump pre-commit from 3.5.0 to 4.0.1 (#2464)
Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 3.5.0 to 4.0.1.
- [Release notes](https://github.com/pre-commit/pre-commit/releases)
- [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md)
- [Commits](https://github.com/pre-commit/pre-commit/compare/v3.5.0...v4.0.1)

---
updated-dependencies:
- dependency-name: pre-commit
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2024-10-22 20:13:49 +02:00
Capa Bot
35767e6c6a Sync capa-testfiles submodule 2024-10-22 15:29:01 +00:00
Fariss
7d8ee6aaac Merge pull request #2455 from s-ff/web-add-releases-workflow 2024-10-22 15:23:37 +02:00
Capa Bot
23709c9d6a Sync capa rules submodule 2024-10-22 13:10:23 +00:00
Moritz
bc72b6d14e Merge pull request #2467 from mandiant/remove-py38-support
update minimum Python to 3.10
2024-10-22 15:09:34 +02:00
Fariss
13b1e533f5 Merge branch 'master' into web-add-releases-workflow 2024-10-22 14:51:02 +02:00
mr-tz
7cc3ddd4ea remove typing_extensions Annotated import 2024-10-22 09:38:33 +00:00
mr-tz
20ae098cda update Python >= 3.10 and ubuntu 2024-10-22 09:38:32 +00:00
mr-tz
2987eeb0ac update type annotations
tmp
2024-10-22 09:38:25 +00:00
mr-tz
cebf8e7274 update minimum Python to 3.10 2024-10-21 15:25:21 +00:00
Capa Bot
d74225b5e0 Sync capa rules submodule 2024-10-18 19:09:29 +00:00
Capa Bot
70610cd1c5 Sync capa rules submodule 2024-10-16 16:11:44 +00:00
Capa Bot
338107cf9e Sync capa rules submodule 2024-10-15 15:04:23 +00:00
Moritz
6b88eed1e4 Merge pull request #2461 from mandiant/fix/idabasesave
fix save base address
2024-10-14 15:49:58 +02:00
Soufiane Fariss
54badc323d ci: add CHANGELOG.md for web releases 2024-10-14 12:55:56 +02:00
Fariss
2e2e1bc277 Merge branch 'master' into web-add-releases-workflow 2024-10-14 12:51:25 +02:00
mr-tz
84c9da09e0 fix save base address 2024-10-14 05:28:48 +00:00
Moritz
b2f89695b5 Merge pull request #2460 from mandiant/fix/idaexplorersave
fix bug preventing save of capa results
2024-10-14 07:24:00 +02:00
mr-tz
bc91171c65 fix bug preventing save of capa results 2024-10-11 15:13:05 +00:00
Moritz
69190dfa82 Merge pull request #2459 from mandiant/mr-tz-patch-2
add v7.4.0 info
2024-10-11 13:03:32 +02:00
Moritz
688afab087 add v7.4.0 info 2024-10-11 12:34:18 +02:00
Fariss
6447319cc7 explorer web: wrap long function calls (#2447)
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2024-10-10 13:40:04 +02:00
Soufiane Fariss
7be6fe6ae1 changelog: add web releases workflow 2024-10-09 18:15:56 +02:00
Soufiane Fariss
ca7073ce87 ci: add web releases workflow 2024-10-09 18:07:48 +02:00
Moritz
1f7f24c467 Merge pull request #2454 from mandiant/fix/ida9idalib
Fix IDA 9.0 / idalib
2024-10-09 18:04:23 +02:00
mr-tz
f2c329b768 rename ida to idapro module for IDA 9.0 2024-10-09 12:20:38 +00:00
mr-tz
22368fbe6f rename bin_search function 2024-10-09 12:13:11 +00:00
Moritz
6a12ab8598 Merge pull request #2450 from mandiant/dependabot/pip/rich-13.9.2
build(deps): bump rich from 13.8.0 to 13.9.2
2024-10-08 10:57:04 +02:00
dependabot[bot]
a4fdb0a3ef build(deps): bump rich from 13.8.0 to 13.9.2
Bumps [rich](https://github.com/Textualize/rich) from 13.8.0 to 13.9.2.
- [Release notes](https://github.com/Textualize/rich/releases)
- [Changelog](https://github.com/Textualize/rich/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Textualize/rich/compare/v13.8.0...v13.9.2)

---
updated-dependencies:
- dependency-name: rich
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-07 14:07:10 +00:00
Moritz
c7bb8b8e67 Update Node checkout Actions (#2446)
* Update setup Node Actions
2024-10-07 11:46:37 +02:00
Tamir K.
41c5194693 Fix/corrupted file architecture key error (#2444)
* Add try except clause
2024-10-06 08:46:16 +02:00
Moritz
8c8b67a6ea Merge pull request #2438 from mandiant/mr-tz-patch-2
Update build.yml
2024-10-04 14:22:45 +02:00
Moritz
f0cc0fb2b8 Update build.yml 2024-10-04 14:02:53 +02:00
Moritz
fc8089c248 Merge pull request #2426 from mandiant/release/v740
Release v7.4.0
2024-10-04 13:51:37 +02:00
mr-tz
d795db9017 include capa explorer web entry 2024-10-04 09:22:11 +00:00
mr-tz
544e3eee5b bump version to 7.4.0
tmp2

tmp2
2024-10-04 09:22:08 +00:00
mr-tz
dfc304d9f6 add Python 3.8 and 3.9 deprecation warning
tmp
2024-10-04 09:19:56 +00:00
Capa Bot
54688517c4 Sync capa rules submodule 2024-10-04 09:18:47 +00:00
Moritz
21fc77ea28 Merge pull request #2431 from s-ff/add-provide-feedback-button
capa Explorer Web: add provide feedback button
2024-10-03 12:28:17 +02:00
Capa Bot
2976974009 Sync capa rules submodule 2024-10-03 09:39:09 +00:00
Moritz
030954d556 Merge pull request #2433 from mandiant/fix/vmray-string-call-args
fix backslash handling in string call arguments
2024-10-03 11:28:34 +02:00
Capa Bot
389a5eb84f Sync capa-testfiles submodule 2024-10-02 16:56:11 +00:00
mr-tz
6d3b96f0b0 fix backslash handling in string call arguments 2024-10-02 16:54:38 +00:00
Soufiane Fariss
2a13bf6c0b capa Explorer Web: fix lint 2024-10-02 16:10:23 +02:00
Fariss
e9f4f5bc31 capa Explorer Web: remove unneeded attribute 2024-10-02 16:05:38 +02:00
Soufiane Fariss
e7400be99a capa Explorer Web: add provide feedback buttom 2024-10-02 15:54:07 +02:00
Moritz
591a1e8fbb Merge pull request #2430 from s-ff/web-fix-import-features
capa Explorer Web: fix import features
2024-10-02 15:29:35 +02:00
Soufiane Fariss
2f5a227fb0 capa Explorer Web: fix import features 2024-10-02 14:49:58 +02:00
Moritz
931ff62421 Merge pull request #2423 from mandiant/dependabot/pip/types-protobuf-5.28.0.20240924
build(deps): bump types-protobuf from 5.27.0.20240920 to 5.28.0.20240924
2024-10-02 11:21:12 +02:00
dependabot[bot]
3037307ee8 build(deps): bump pydantic from 2.9.1 to 2.9.2 (#2389)
* build(deps): bump pydantic from 2.9.1 to 2.9.2

Bumps [pydantic](https://github.com/pydantic/pydantic) from 2.9.1 to 2.9.2.
- [Release notes](https://github.com/pydantic/pydantic/releases)
- [Changelog](https://github.com/pydantic/pydantic/blob/main/HISTORY.md)
- [Commits](https://github.com/pydantic/pydantic/compare/v2.9.1...v2.9.2)

---
updated-dependencies:
- dependency-name: pydantic
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update requirements.txt

* remove pinned sub-dependency

Co-authored-by: Willi Ballenthin <wballenthin@google.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
Co-authored-by: Willi Ballenthin <wballenthin@google.com>
2024-10-02 11:20:54 +02:00
Capa Bot
d6c1725d7e Sync capa rules submodule 2024-10-02 08:41:23 +00:00
Fariss
16eae70c17 capa Explorer Web: improve url navigation (#2425)
* explorer web: improve url navigation

This commit enhances the navigation guard for the /analysis route to
provide a better user experience when loading data from a URL:

Previously: users browsing to /analysis were always redirected to
the homepage (/).

With this commit:
- If a user accesses /analysis without an rdoc parameter, they are still
  redirected to the homepage.
- If a user accesses /analysis with an rdoc parameter, the following
  occurs:
  The user is redirected to the homepage (/) and the rdoc parameter is
  preserved in the URL, capa Explorer Web then loads the rdoc from URL.

---------

Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2024-10-01 19:25:20 +02:00
dependabot[bot]
9e7e6be374 build(deps): bump types-protobuf from 5.27.0.20240920 to 5.28.0.20240924
Bumps [types-protobuf](https://github.com/python/typeshed) from 5.27.0.20240920 to 5.28.0.20240924.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-protobuf
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-01 11:44:19 +00:00
Moritz
3e8bed1db2 Merge pull request #2421 from mandiant/ci/dependabot-ignore-patch
Update dependabot.yml to ignore patch versions
2024-10-01 13:40:34 +02:00
Moritz
e4ac02a968 Update dependabot.yml 2024-10-01 13:32:31 +02:00
dependabot[bot]
eff358980a build(deps): bump pefile from 2023.2.7 to 2024.8.26 (#2413) 2024-09-30 20:24:09 +00:00
Capa Bot
108bd7f224 Sync capa-testfiles submodule 2024-09-30 12:08:25 +00:00
Willi Ballenthin
ab43c8c0c2 loader: fix unhandled name error (#2411) 2024-09-30 14:06:14 +02:00
Capa Bot
585dff8b48 Sync capa rules submodule 2024-09-30 12:06:04 +00:00
Capa Bot
cb09041387 Sync capa rules submodule 2024-09-30 12:05:43 +00:00
Capa Bot
80899f3f70 Sync capa-testfiles submodule 2024-09-27 09:53:30 +00:00
Moritz
00d2bb06fd Merge pull request #2409 from mandiant/fix/2408
dynamic: emit complete features for A/W APIs
2024-09-27 11:26:39 +02:00
Moritz
ff1043e976 Merge branch 'master' into fix/2408 2024-09-27 09:35:24 +02:00
Fariss
51a4eb46b8 replace tqdm, termcolor, tabulate with rich (#2374)
* logging: use rich handler for logging

* tqdm: remove unneeded redirecting_print_to_tqdm function

* tqdm: introduce `CapaProgressBar` rich `Progress` bar

* tqdm: replace tqdm with rich Progress bar

* tqdm: remove tqdm dependency

* termcolor: replace termcolor and update `scripts/`

* tests: update `test_render.py` to use rich.console.Console

* termcolor: remove termcolor dependency

* capa.render.utils: add `write` & `writeln` methods to subclass `Console`

* update markup util functions to use fmt strings

* tests: update `test_render.py` to use `capa.render.utils.Console`

* replace kwarg `end=""` with `write` and `writeln` methods

* tabulate: replace tabulate with `rich.table`

* tabulate: remove `tabulate` and its dependency `wcwidth`

* logging: handle logging in `capa.main`

* logging: set up logging in `capa.main`

this commit sets up logging in `capa.main` and uses a shared
`log_console` in `capa.helpers` for logging purposes

* changelog: replace packages with rich

* remove entry from pyinstaller and unneeded progress.update call

* update requirements.txt

* scripts: use `capa.helpers.log_console` in `CapaProgressBar`

* logging: configure root logger to use `RichHandler`

* remove unused import `inspect`
2024-09-27 09:34:21 +02:00
dependabot[bot]
558bf0fbf2 build(deps): bump protobuf from 5.27.3 to 5.28.2 (#2390)
Bumps [protobuf](https://github.com/protocolbuffers/protobuf) from 5.27.3 to 5.28.2.
- [Release notes](https://github.com/protocolbuffers/protobuf/releases)
- [Changelog](https://github.com/protocolbuffers/protobuf/blob/main/protobuf_release.bzl)
- [Commits](https://github.com/protocolbuffers/protobuf/compare/v5.27.3...v5.28.2)

---
updated-dependencies:
- dependency-name: protobuf
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-27 09:32:58 +02:00
dependabot[bot]
76aff57467 build(deps): bump setuptools from 70.0.0 to 75.1.0 (#2392)
Bumps [setuptools](https://github.com/pypa/setuptools) from 70.0.0 to 75.1.0.
- [Release notes](https://github.com/pypa/setuptools/releases)
- [Changelog](https://github.com/pypa/setuptools/blob/main/NEWS.rst)
- [Commits](https://github.com/pypa/setuptools/compare/v70.0.0...v75.1.0)

---
updated-dependencies:
- dependency-name: setuptools
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-27 09:32:18 +02:00
dependabot[bot]
f82fc1902c build(deps): bump types-protobuf from 5.27.0.20240907 to 5.27.0.20240920 (#2393)
Bumps [types-protobuf](https://github.com/python/typeshed) from 5.27.0.20240907 to 5.27.0.20240920.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-protobuf
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-27 09:32:08 +02:00
Capa Bot
e9e8fe42ed Sync capa rules submodule 2024-09-27 07:31:51 +00:00
Mike Hunhoff
80e007787c dynamic: update CHANGELOG 2024-09-26 14:43:20 -06:00
Mike Hunhoff
bfcc705117 dynamic: vmray: remove redundant test 2024-09-26 14:42:08 -06:00
Mike Hunhoff
834150ad1d dynamic: drakvuf: fix A/W API detection 2024-09-26 14:36:16 -06:00
Mike Hunhoff
31ec208a9b dynamic: cape: fix A/W API detection 2024-09-26 14:27:45 -06:00
Mike Hunhoff
a5d9459c42 dynamic: vmray: fix A/W API detection 2024-09-26 14:15:21 -06:00
Moritz
06271a88d4 Fix VMRay missing process data (#2396)
* get all processes, see #2394

* add tests for process recording

* rename symbols for clarification

* handle single and list entries

* update changelog

* dynamic: vmray: use monitor IDs to track processes and threads

* dynamic: vmray: code refactor

* dynamic: vmray: add sanity checks when processing monitor processes

* dynamic: vmray: remove unnecessary keys() access

* dynamic: vmray: clarify comments

* Update CHANGELOG.md

Co-authored-by: Willi Ballenthin <wballenthin@google.com>

* dynamic: vmray: update CHANGELOG

---------

Co-authored-by: Mike Hunhoff <mike.hunhoff@gmail.com>
Co-authored-by: Willi Ballenthin <wballenthin@google.com>
2024-09-26 13:57:30 -06:00
Capa Bot
c48bccf623 Sync capa rules submodule 2024-09-26 17:38:34 +00:00
Capa Bot
9975f769f9 Sync capa-testfiles submodule 2024-09-26 17:34:51 +00:00
Capa Bot
c5d8f99d6f Sync capa rules submodule 2024-09-26 12:25:36 +00:00
Willi Ballenthin
bcd57a9af1 detect and use third-party analysis backends when possible (#2380)
* introduce script to detect 3P backends

ref #2376

* add idalib backend

* binary ninja: search for API using XDG desktop entry

ref #2376

* binja: search more XDG locations for desktop entry

* binary ninja: optimize embedded PE scanning

closes #2397

* add script for comparing the performance of analysis backends
2024-09-26 13:21:55 +02:00
Capa Bot
12337be2b7 Sync capa-testfiles submodule 2024-09-25 09:17:50 +00:00
Moritz
25c4902c21 Merge pull request #2400 from mandiant/web/filesize
bump upload size limit to 100MB from 10MB
2024-09-24 14:14:42 +02:00
mr-tz
f024e1d54c bump upload size limit to 100MB from 10MB 2024-09-24 12:09:38 +00:00
Moritz
bab7ed9188 Merge pull request #2395 from mandiant/dependabot/npm_and_yarn/web/explorer/rollup-4.22.4
build(deps): bump rollup from 4.21.3 to 4.22.4 in /web/explorer
2024-09-24 13:49:10 +02:00
Capa Bot
6eda8c9713 Sync capa-testfiles submodule 2024-09-24 11:29:53 +00:00
Capa Bot
22e88c860f Sync capa-testfiles submodule 2024-09-24 11:25:28 +00:00
Capa Bot
7884248022 Sync capa rules submodule 2024-09-24 11:25:18 +00:00
dependabot[bot]
4891fd750f build(deps): bump rollup from 4.21.3 to 4.22.4 in /web/explorer
Bumps [rollup](https://github.com/rollup/rollup) from 4.21.3 to 4.22.4.
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.21.3...v4.22.4)

---
updated-dependencies:
- dependency-name: rollup
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-24 04:55:36 +00:00
Willi Ballenthin
783e14b949 pyinstaller: use Python 3.12 for standalone build (#2385)
* pyinstaller: use Python 3.12 for standalone build

closes #2383

* changelog

* ci: build: fix test filename
2024-09-23 22:33:23 +02:00
Willi Ballenthin
74777ad23e changelog 2024-09-23 20:21:50 +00:00
Willi Ballenthin
01b35e7582 pyproject.toml: bump min python version to 3.8.1
fixed #2387
2024-09-23 20:21:50 +00:00
Capa Bot
e29288cc8d Sync capa rules submodule 2024-09-22 12:09:30 +00:00
Moritz
c4c35ca6e9 Merge pull request #2379 from mandiant/weg/update-homepage
update release v7.3.0 info and formatting
2024-09-20 14:46:42 +02:00
Moritz
3b1e0284c0 Merge pull request #2378 from mandiant/doc/update-homepage
add update homepage entry
2024-09-20 14:46:27 +02:00
Moritz
7b61d28dd2 Merge pull request #2375 from mandiant/dependabot/npm_and_yarn/web/explorer/vite-5.4.6
build(deps-dev): bump vite from 5.3.2 to 5.4.6 in /web/explorer
2024-09-20 12:02:31 +02:00
mr-tz
e3267df5b1 update release v7.3.0 info and formatting 2024-09-20 09:57:01 +00:00
Moritz
9076e5475d add update homepage entry 2024-09-20 11:14:16 +02:00
Moritz
d1d8badc2e Merge pull request #2370 from mandiant/release/v730
bump to v7.3.0
2024-09-20 10:41:27 +02:00
dependabot[bot]
84d2a18b52 build(deps-dev): bump vite from 5.3.2 to 5.4.6 in /web/explorer
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.3.2 to 5.4.6.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.4.6/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.4.6/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-17 19:16:36 +00:00
mr-tz
954aeb0ce4 bump to v7.3.0 2024-09-17 15:04:00 +00:00
Moritz
882a68bbd4 Merge pull request #2373 from mandiant/fix/ida75compat
fix byte search IDA Pro 7.5 compatability
2024-09-17 16:36:11 +02:00
Moritz
3d2d436d92 Merge branch 'master' into fix/ida75compat 2024-09-17 16:31:48 +02:00
mr-tz
1c64001ed8 fix byte search IDA Pro 7.5 compatability 2024-09-17 12:53:27 +00:00
Moritz
ab20366e2d Merge pull request #2372 from mandiant/dependabot/pip/pydantic-2.9.1
build(deps): bump pydantic from 2.7.3 to 2.9.1
2024-09-17 12:57:12 +02:00
Moritz
ce3ba8ec3c bump pydantic-core to 2.23.3 2024-09-17 11:54:47 +02:00
dependabot[bot]
fe6995a687 build(deps): bump pydantic from 2.7.3 to 2.9.1
Bumps [pydantic](https://github.com/pydantic/pydantic) from 2.7.3 to 2.9.1.
- [Release notes](https://github.com/pydantic/pydantic/releases)
- [Changelog](https://github.com/pydantic/pydantic/blob/main/HISTORY.md)
- [Commits](https://github.com/pydantic/pydantic/compare/v2.7.3...v2.9.1)

---
updated-dependencies:
- dependency-name: pydantic
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-17 09:52:46 +00:00
Moritz
4d812f085f Merge pull request #2366 from mandiant/dependabot/pip/types-protobuf-5.27.0.20240907
build(deps): bump types-protobuf from 5.27.0.20240626 to 5.27.0.20240907
2024-09-17 11:45:16 +02:00
Moritz
6c8791a541 Merge pull request #2369 from mandiant/dependabot/pip/build-1.2.2
build(deps): bump build from 1.2.1 to 1.2.2
2024-09-17 11:45:06 +02:00
Capa Bot
25111f8a95 Sync capa rules submodule 2024-09-16 15:49:25 +00:00
dependabot[bot]
38fa7f0b80 build(deps): bump build from 1.2.1 to 1.2.2
Bumps [build](https://github.com/pypa/build) from 1.2.1 to 1.2.2.
- [Release notes](https://github.com/pypa/build/releases)
- [Changelog](https://github.com/pypa/build/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pypa/build/compare/1.2.1...1.2.2)

---
updated-dependencies:
- dependency-name: build
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-16 15:00:39 +00:00
dependabot[bot]
6ebbd1db89 build(deps): bump types-protobuf from 5.27.0.20240626 to 5.27.0.20240907
Bumps [types-protobuf](https://github.com/python/typeshed) from 5.27.0.20240626 to 5.27.0.20240907.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-protobuf
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-16 15:00:28 +00:00
Capa Bot
93fbdbb51f Sync capa rules submodule 2024-09-16 12:45:49 +00:00
Capa Bot
adb339419d Sync capa rules submodule 2024-09-16 12:43:59 +00:00
Capa Bot
25ca29573c Sync capa-testfiles submodule 2024-09-16 12:18:40 +00:00
Yacine
f4f0347473 Add msgspec to requirements.txt (#2360)
* add msgspec to requirements.txt
2024-09-13 17:24:25 +02:00
Capa Bot
dc97f5abb5 Sync capa rules submodule 2024-09-13 13:05:30 +00:00
Capa Bot
8b22a7fca2 Sync capa-testfiles submodule 2024-09-13 12:59:45 +00:00
Willi Ballenthin
ee17d75be9 implement BinExport2 backend (#1950)
* elf: os: detect Android via clang compiler .ident note

* elf: os: detect Android via dependency on liblog.so

* main: split main into a bunch of "main routines"

[wip] since there are a few references to BinExport2
that are in progress elsewhre. Next commit will remove them.

* features: add BinExport2 declarations

* BinExport2: initial skeleton of feature extraction

* main: remove references to wip BinExport2 code

* changelog

* main: rename first position argument "input_file"

closes #1946

* main: linters

* main: move rule-related routines to capa.rules

ref #1821

* main: extract routines to capa.loader module

closes #1821

* add loader module

* loader: learn to load freeze format

* freeze: use new cli arg handling

* Update capa/loader.py

Co-authored-by: Moritz <mr-tz@users.noreply.github.com>

* main: remove duplicate documentation

* main: add doc about where some functions live

* scripts: migrate to new main wrapper helper functions

* scripts: port to main routines

* main: better handle auto-detection of backend

* scripts: migrate bulk-process to main wrappers

* scripts: migrate scripts to main wrappers

* main: rename *_from_args to *_from_cli

* changelog

* cache-ruleset: remove duplication

* main: fix tag handling

* cache-ruleset: fix cli args

* cache-ruleset: fix special rule cli handling

* scripts: fix type bytes

* main: nicely format debug messages

* helpers: ensure log messages aren't very long

* flake8 config

* binexport2: formatting

* loader: learn to load BinExport2 files

* main: debug log the format and backend

* elf: add more arch constants

* binexport: parse global features

* binexport: extract file features

* binexport2: begin to enumerate function/bb/insns

* binexport: pass context to function/bb/insn extractors

* binexport: linters

* binexport: linters

* scripts: add script to inspect binexport2 file

* inspect-binexport: fix xref symbols

* inspect-binexport: factor out the index building

* binexport: move index to binexport extractor module

* binexport: implement ELF/aarch64 GOT/thunk analyzer

* binexport: implement API features

* binexport: record the full vertex for a thunk

* binexport: learn to extract numbers

* binexport: number: skipped mapped numbers

* binexport: fix basic block address indexing

* binexport: rename function

* binexport: extract operand numbers

* binexport: learn to extract calls from characteristics

* binexport: learn to extract mnemonics

* pre-commit: skip protobuf file

* binexport: better search for sample file

* loader: add file extractors for BinExport2

* binexport: remove extra parameter

* new black config

* binexport: index string xrefs

* binexport: learn to extract bytes and strings

* binexport: cache parsed PE/ELF

* binexport: handle Ghidra SYMBOL numbers

* binexport2: handle binexport#78 (Ghidra only uses SYMBOL expresssions)

* main: write error output to stderr, not stdout

* scripts: add example detect-binexport2-capabilities.py

* detect-binexport2-capabilities: more documentation/examples

* elffile: recognize more architectures

* binexport: handle read_memory errors

* binexport: index flow graphs by address

* binexport: cleanup logging

* binexport: learn to extract function names

* binexport: learn to extract all function features

* binexport: learn to extract bb tight loops

* elf: don't require vivisect just for type annotations

* main: remove unused imports

* rules: don't eagerly import ruamel until needed

* loader: avoid eager imports of some backend-related code

* changelog

* fmt

* binexport: better render optional fields

* fix merge conflicts

* fix formatting

* remove Ghidra data reference madness

* handle PermissionError when searching sample file for BinExport2 file

* handle PermissionError when searching sample file for BinExport2 file

* add Android as valid OS

* inspect-binexport: strip strings

* inspect-binexport: render operands

* fix lints

* ruff: update config layout

* inspect-binexport: better align comments/xrefs

* use explicit search paths to get sample for BinExport file

* add initial BinExport tests

* add/update BinExport tests and minor fixes

* inspect-binexport: add perf tracking

* inspect-binexport: cache rendered operands

* lints

* do not extract number features for ret instructions

* Fix BinExport's "tight loop" feature extraction.

`idx.target_edges_by_basic_block_index[basic_block_index]` is of type
`List[Edges]`. The index `basic_block_index` was definitely not an
element.

* inspect-binexport: better render data section

* linters

* main: accept --format=binexport2

* binexport: insn: add support for parsing bare immediate int operands

* binexport2: bb: fix tight loop detection

ref #2050

* binexport: api: generate variations of Win32 APIs

* lints

* binexport: index: don't assume instruction index is 1:1 with address

* be2: index instruction addresses

* be2: temp remove bytes feature processing

* binexport: read memory from an address space extracted from PE/ELF

closes #2061

* be2: resolve thunks to imported functions

* be2: check for be2 string reference before bytes/string extraction overhead

* be2: remove unneeded check

* be2: do not process thunks

* be2: insn: polish thunk handling a bit

* be2: pre-compute thunk targets

* parse negative numbers

* update tests to use Ghidra-generated BinExport file

* remove unused import

* black reformat

* run tests always (for now)

* binexport: tests: fix test case

* binexport: extractor: fix insn lint

* binexport: addressspace: use base address recovered from binexport file

* Add nzxor charecteristic in BinExport extractor.

by referencing vivisect implementation.

* add tests, fix stack cookie detection

* test BinExport feature PRs

* reformat and fix

* complete TODO descriptions

* wip tests

* binexport: add typing where applicable (#2106)

* binexport2: revert import names from BinExport2 proto

binexport2_pb.BinExport2 isnt a package so we can't import it like:

    from ...binexport2_pb.BinExport2 import CallGraph

* fix stack offset numbers and disable offset tests

* xfail OperandOffset

* generate symbol variants

* wip: read negative numbers

* update tight loop tests

* binexport: fix function loop feature detection

* binexport: update binexport function loop tests

* binexport: fix lints and imports

* binexport: add back assert statement to thunk calculation

* binexport: update tests to use Ghidra binexport file

* binexport: add additional debug info to thunk calculation assert

* binexport: update unit tests to focus on Ghidra

* binexport: fix lints

* binexport: remove Ghidra symbol madness and fix x86/amd64 stack offset number tests

* binexport: use masking for Number features

* binexport: ignore call/jmp immediates for intel architecture

* binexport: check if immediate is a mapped address

* binexport: emit offset features for immediates likely structure offsets

* binexport: add twos complement wrapper insn.py

* binexport: add support for x86 offset features

* binexport: code refactor

* binexport: init refactor for multi-arch instruction feature parsing

* binexport: intel: emit indirect call characteristic

* binexport: use helper method for instruction mnemonic

* binexport: arm: emit offset features from stp instruction

* binexport: arm: emit indirect call characteristic

* binexport: arm: improve offset feature extraction

* binexport: add workaroud for Ghidra bug that results in empty operands (no expressions)

* binexport: skip x86 stack string tests

* binexport: update mimikatz.exe_ feature count tests for Ghidra

* core: loader: update binja import

* core: loader: update binja imports

* binexport: arm: ignore number features for add instruction manipulating stack

* binexport: update unit tests

* binexport: arm: ignore number features for sub instruction manipulating stack

* binexport: arm: emit offset features for add instructions

* binexport: remove TODO from tests workflow

* binexport: update CHANGELOG

* binexport: remove outdated TODOs

* binexport: re-enable support for data references in inspect-binexport2.py

* binexport: skip data references to code

* binexport: remove outdated TODOs

* Update scripts/inspect-binexport2.py

* Update CHANGELOG.md

* Update capa/helpers.py

* Update capa/features/extractors/common.py

* Update capa/features/extractors/binexport2/extractor.py

* Update capa/features/extractors/binexport2/arch/arm/insn.py

Co-authored-by: Moritz <mr-tz@users.noreply.github.com>

* initial add

* test binexport scripts

* add tests using small ARM ELF

* add method to get instruction by address

* index instructions by address

* adjust and extend tests

* handle operator with no children bug

* binexport: use instruction address index

ref: https://github.com/mandiant/capa/pull/1950/files#r1728570811

* inspect binexport: handle lsl with no children

add pruning phase to expression tree building
to remove known-bad branches. This might address
some of the data we're seeing due to:
https://github.com/NationalSecurityAgency/ghidra/issues/6821

Also introduces a --instruction optional argument
to dump the details of a specific instruction.

* binexport: consolidate expression tree logic into helpers

* binexport: index instruction indices by address

* binexport: introduce instruction pattern matching

Introduce intruction pattern matching to declaratively
describe the instructions and operands that we want to
extract. While there's a bit more code, its much more
thoroughly tested, and is less brittle than the prior
if/else/if/else/if/else implementation.

* binexport: helpers: fix missing comment words

* binexport: update tests to reflect updated test files

* remove testing of feature branch

---------

Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
Co-authored-by: Mike Hunhoff <mike.hunhoff@gmail.com>
Co-authored-by: mr-tz <moritz.raabe@mandiant.com>
Co-authored-by: Lin Chen <larch.lin.chen@gmail.com>
2024-09-12 10:09:05 -06:00
Capa Bot
2fc0783faa Sync capa-testfiles submodule 2024-09-12 14:56:13 +00:00
Moritz
e07ff1c76c Update web pages (#2354)
* extend descriptions and improve styling

* s/capa explorer web/capa Explorer Web

* set htmlWhitespaceSensitivity to ignore and reformat
2024-09-11 20:28:04 +02:00
Capa Bot
f236afe2a6 Sync capa rules submodule 2024-09-11 15:42:34 +00:00
Capa Bot
9b64afab60 Sync capa rules submodule 2024-09-11 15:39:57 +00:00
Moritz
c9f5188c01 Merge pull request #2356 from williballenthin/push-muzpypqtrssq
cache: use path to code, not hardcoded relative path
2024-09-11 14:58:51 +02:00
Willi Ballenthin
51d2ea147b cache: use path to code, not hardcoded relative path
closes #2350
2024-09-11 11:37:39 +00:00
dependabot[bot]
7b101b33dc build(deps): bump vivisect from 1.1.1 to 1.2.1 (#2345)
* build(deps): bump vivisect from 1.1.1 to 1.2.1

Bumps [vivisect](https://github.com/vivisect/vivisect) from 1.1.1 to 1.2.1.
- [Changelog](https://github.com/vivisect/vivisect/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/vivisect/vivisect/compare/v1.1.1...v1.2.1)

---
updated-dependencies:
- dependency-name: vivisect
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump pyasn1 versions

* Bump cxxfilt version

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2024-09-11 11:18:02 +02:00
Fariss
e70d5b3e27 webui: show capabilities by function - make function count reactive (#2352)
* web explorer: make function count reflective when show-lib-func is
toggled on/off

* introduce match-count class to mute and minimize match count text labels

* fix typo
2024-09-10 16:46:42 +02:00
dependabot[bot]
529a5de534 build(deps): bump deptry from 0.19.1 to 0.20.0 (#2344)
Bumps [deptry](https://github.com/fpgmaas/deptry) from 0.19.1 to 0.20.0.
- [Release notes](https://github.com/fpgmaas/deptry/releases)
- [Changelog](https://github.com/fpgmaas/deptry/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fpgmaas/deptry/compare/0.19.1...0.20.0)

---
updated-dependencies:
- dependency-name: deptry
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2024-09-10 12:56:04 +02:00
Moritz
9459251e12 use new IDAPython 9.0 APIs (#2339)
* use new IDAPython 9.0 APIs

* add IDAPython compatibility wrappers
2024-09-10 12:55:42 +02:00
Moritz
113b2593fa Merge pull request #2351 from mandiant/dependabot/pip/ruff-0.6.4
build(deps): bump ruff from 0.6.2 to 0.6.4
2024-09-10 12:11:19 +02:00
Moritz
80cae197d1 Merge pull request #2347 from mandiant/dependabot/pip/types-psutil-6.0.0.20240901
build(deps): bump types-psutil from 6.0.0.20240621 to 6.0.0.20240901
2024-09-10 12:10:48 +02:00
dependabot[bot]
923132b9b7 build(deps): bump rich from 13.7.1 to 13.8.0 (#2343)
Bumps [rich](https://github.com/Textualize/rich) from 13.7.1 to 13.8.0.
- [Release notes](https://github.com/Textualize/rich/releases)
- [Changelog](https://github.com/Textualize/rich/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Textualize/rich/compare/v13.7.1...v13.8.0)

---
updated-dependencies:
- dependency-name: rich
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2024-09-10 11:30:16 +02:00
dependabot[bot]
363e70f523 build(deps): bump ruff from 0.6.2 to 0.6.4
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.6.2 to 0.6.4.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.6.2...0.6.4)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-09 14:04:36 +00:00
Capa Bot
eab3ff8726 Sync capa-testfiles submodule 2024-09-09 13:45:30 +00:00
Capa Bot
f1453eac59 Sync capa-testfiles submodule 2024-09-09 08:57:36 +00:00
Capa Bot
44e6594a1c Sync capa-testfiles submodule 2024-09-09 08:31:06 +00:00
dependabot[bot]
a4e81540d1 build(deps): bump types-psutil from 6.0.0.20240621 to 6.0.0.20240901
Bumps [types-psutil](https://github.com/python/typeshed) from 6.0.0.20240621 to 6.0.0.20240901.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-psutil
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-02 14:49:59 +00:00
Fariss
68e07fbb9a web: omit unneeded function param in createCapaRulesUrl (#2342) 2024-08-29 10:42:23 -06:00
Willi Ballenthin
729a1a85b7 cli: link to rule names to capa rules website (#2338)
* web: rules: redirect from various rule names to canonical rule URL

closes #2319

Update index.html

Co-authored-by: Moritz <mr-tz@users.noreply.github.com>

* cli: link to rule names to capa rules website

* just: make `just lint` run all steps, not fail on first error

---------

Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2024-08-29 16:56:14 +02:00
Moritz
db4798aaf6 Merge pull request #2335 from mandiant/dependabot/pip/pygithub-2.4.0
build(deps): bump pygithub from 2.3.0 to 2.4.0
2024-08-27 12:13:26 +02:00
Moritz
ce62fecbea Merge pull request #2336 from mandiant/dependabot/pip/flake8-bugbear-24.8.19
build(deps): bump flake8-bugbear from 24.4.26 to 24.8.19
2024-08-27 12:13:11 +02:00
Moritz
138c7014e5 Merge pull request #2334 from mandiant/dependabot/pip/ruff-0.6.2
build(deps): bump ruff from 0.5.6 to 0.6.2
2024-08-27 12:12:51 +02:00
Moritz
9d8401a9a7 Merge pull request #2333 from mandiant/dependabot/pip/mypy-1.11.2
build(deps): bump mypy from 1.11.1 to 1.11.2
2024-08-27 12:12:44 +02:00
Moritz
0db53e5086 Merge pull request #2332 from mandiant/dependabot/pip/pyyaml-6.0.2
build(deps): bump pyyaml from 6.0.1 to 6.0.2
2024-08-27 12:12:35 +02:00
Moritz
3223d3f24f Merge pull request #2208 from mandiant/vmray-extractor
dynamic: add extractor for VMRay dynamic sandbox traces
2024-08-27 12:11:36 +02:00
dependabot[bot]
b1a79fba9d build(deps): bump flake8-bugbear from 24.4.26 to 24.8.19
Bumps [flake8-bugbear](https://github.com/PyCQA/flake8-bugbear) from 24.4.26 to 24.8.19.
- [Release notes](https://github.com/PyCQA/flake8-bugbear/releases)
- [Commits](https://github.com/PyCQA/flake8-bugbear/compare/24.4.26...24.8.19)

---
updated-dependencies:
- dependency-name: flake8-bugbear
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-26 14:48:38 +00:00
dependabot[bot]
770fefbba8 build(deps): bump pygithub from 2.3.0 to 2.4.0
Bumps [pygithub](https://github.com/pygithub/pygithub) from 2.3.0 to 2.4.0.
- [Release notes](https://github.com/pygithub/pygithub/releases)
- [Changelog](https://github.com/PyGithub/PyGithub/blob/main/doc/changes.rst)
- [Commits](https://github.com/pygithub/pygithub/compare/v2.3.0...v2.4.0)

---
updated-dependencies:
- dependency-name: pygithub
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-26 14:48:34 +00:00
dependabot[bot]
3108ac0928 build(deps): bump ruff from 0.5.6 to 0.6.2
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.5.6 to 0.6.2.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.5.6...0.6.2)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-26 14:48:29 +00:00
dependabot[bot]
7e7d511201 build(deps): bump mypy from 1.11.1 to 1.11.2
Bumps [mypy](https://github.com/python/mypy) from 1.11.1 to 1.11.2.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.11.1...v1.11.2)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-26 14:48:15 +00:00
dependabot[bot]
6d6c245241 build(deps): bump pyyaml from 6.0.1 to 6.0.2
Bumps [pyyaml](https://github.com/yaml/pyyaml) from 6.0.1 to 6.0.2.
- [Release notes](https://github.com/yaml/pyyaml/releases)
- [Changelog](https://github.com/yaml/pyyaml/blob/main/CHANGES)
- [Commits](https://github.com/yaml/pyyaml/compare/6.0.1...6.0.2)

---
updated-dependencies:
- dependency-name: pyyaml
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-26 14:48:08 +00:00
Moritz
fa92cfd43d Merge branch 'master' into vmray-extractor 2024-08-26 16:18:34 +02:00
Fariss
ed5dd38e7e feat: auto-generate ruleset cache on source change (#2133)
* feat: auto-generate ruleset cache on source change

---------

Co-authored-by: mr-tz <moritz.raabe@mandiant.com>
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
Co-authored-by: Willi Ballenthin <wballenthin@google.com>
2024-08-26 14:01:10 +02:00
Fariss
b4f60eca64 web: fix class feature type (#2331) 2024-08-26 05:12:55 -06:00
mr-tz
e46811685d Merge branch 'vmray-extractor' of github.com:mandiant/capa into vmray-extractor 2024-08-26 10:54:36 +00:00
Moritz
6ce130e6da Merge branch 'master' into vmray-extractor 2024-08-26 12:34:03 +02:00
Capa Bot
a380609514 Sync capa-testfiles submodule 2024-08-26 10:30:55 +00:00
Moritz
e71f90c618 dos2unix (#2330) 2024-08-26 12:22:06 +02:00
mr-tz
9eab7eb143 update names 2024-08-26 10:11:51 +00:00
mr-tz
e8550f242c rename using dashes for consistency 2024-08-26 09:55:00 +00:00
Moritz
d98c315eb4 Merge branch 'master' into vmray-extractor 2024-08-26 11:31:18 +02:00
Fariss
a779cf2a28 cli: add note about capa explorer web to CLI help text (#2329)
* cli: add note about capa explorer web to CLI help text

---------

Co-authored-by: Willi Ballenthin <wballenthin@google.com>
2024-08-26 09:22:55 +02:00
Moritz
a5c14c32b8 Merge pull request #2312 from s-ff/edit-explorer-landing-page
Edit explorer landing page
2024-08-23 17:30:38 +02:00
Fariss
88a632c2d4 Update web/explorer/README.md
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2024-08-23 17:21:12 +02:00
Fariss
89443742cd Update web/explorer/README.md
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2024-08-23 17:21:06 +02:00
Soufiane Fariss
1ffee81cea introduce getting started step to explorer landing page 2024-08-23 17:13:43 +02:00
Willi Ballenthin
6c883f37a8 add .justfile (#2325) 2024-08-22 13:25:53 +02:00
Moritz
dcc74eb07a Merge pull request #2326 from mandiant/williballenthin-patch-1
readme: add quick links to header
2024-08-22 13:25:06 +02:00
Moritz
0a6bc20eed Merge pull request #2324 from williballenthin/fix/2323
rules: deduplicate API features with stripped DLL
2024-08-22 13:22:05 +02:00
dependabot[bot]
df3c265bd5 build(deps): bump deptry from 0.17.0 to 0.19.1 (#2303)
Bumps [deptry](https://github.com/fpgmaas/deptry) from 0.17.0 to 0.19.1.
- [Release notes](https://github.com/fpgmaas/deptry/releases)
- [Changelog](https://github.com/fpgmaas/deptry/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fpgmaas/deptry/compare/0.17.0...0.19.1)

---
updated-dependencies:
- dependency-name: deptry
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
Co-authored-by: Willi Ballenthin <wballenthin@google.com>
2024-08-22 13:18:19 +02:00
dependabot[bot]
73120a5c0b build(deps): bump humanize from 4.9.0 to 4.10.0 (#2304)
Bumps [humanize](https://github.com/python-humanize/humanize) from 4.9.0 to 4.10.0.
- [Release notes](https://github.com/python-humanize/humanize/releases)
- [Commits](https://github.com/python-humanize/humanize/compare/4.9.0...4.10.0)

---
updated-dependencies:
- dependency-name: humanize
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-22 13:16:45 +02:00
dependabot[bot]
a0ed2127f9 build(deps): bump flake8 from 7.1.0 to 7.1.1 (#2306)
Bumps [flake8](https://github.com/pycqa/flake8) from 7.1.0 to 7.1.1.
- [Commits](https://github.com/pycqa/flake8/compare/7.1.0...7.1.1)

---
updated-dependencies:
- dependency-name: flake8
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-22 13:16:36 +02:00
Willi Ballenthin
4df8b2b7ed readme: add quick links to header
closes #2321
2024-08-22 13:11:07 +02:00
Willi Ballenthin
68a38b6e6f rules: deduplicate API features with stripped DLL
closes #2323
2024-08-22 10:34:53 +00:00
Willi Ballenthin
a33f67b48e add landing page and rules website (#2310)
* web: index: add gif of capa running

* index: add screencast of running capa

produced via:

```
asciinema capa.cast
./capa Practical\ Malware\ Analysis\ Lab\ 01-01.dll_
<ctrl-d>
agg --no-loop --theme solarized-light capa.cast capa.gif
```

* web: index: start to sketch out style

* web: landing page

* web: merge rules website

* web: rules: update bootstrap and integrate rules

* web: rules: use pygments to syntax highlight rules

Use the Pygments syntax-highlighting library to parse
and render the YAML rule content. This way we don't have
to manually traverse the rule nodes and emit lists; instead,
we rely on the fact that YAML is pretty easy for humans
to read and let them consume it directly, with some text 
formatting to help hint at the types/structure.

* web: rules: use capa to load rule content

capa (the library) has routines for deserializing the YAML
content into structured objects, which means we can use tools
like mypy to find bugs. So, prefer to use those routines instead
of parsing YAML ourselves.

* web: rules: linters

Run and fix the issues identified by the following linters:

  - isort
  - black
  - ruff
  - mypy

* web: rules: add some links to rule page

Add links to the following external resources:

  - GitHub rule source in capa-rules repo
  - VirusTotal search for matching samples

* web: rules: accept ?q= parameter for initial search

Update the rules landing page to accept a HTTP
query parameter named "q" that specifies an initial 
search term to to pass to pagefind. This enables
external pages link to rule searches.

* web: rules: add link to namespace search

* web: rules: use consistent header

Import header from root capa landing page.

* web: rules: add umami script

* web: add initial whats new section, TODOs

* web: rules: remove old images

* changelog

* CI: remove temporary branch push event triggers

* Delete web/rules/public/css/bootstrap-4.5.2.min.css

* Delete web/rules/public/js/bootstrap-4.5.2.min.js

* Delete web/public/img/capa.cast

* Rename readme.md to README.md

* web: rules: add scripts to pre-commit configs

* web: rules: add scripts to pre-commit configs

* lints

* ci: add temporary branch push trigger to get incremental builds

* web: rules: assert start_dir must exist

* ci: web: rules: deep checkout so we can get rule history

* web: rules: check output of subprocess

* web: rules: factor out common CSS

* web: rules: fix header links

* web: rules: only index rule content, not surrounding text

* ci: web: remote temporary branch push trigger
2024-08-22 09:42:40 +02:00
Soufiane Fariss
f2ed09861e web: modify theming and add info to landing page 2024-08-21 18:49:26 +02:00
Soufiane Fariss
5b583bdf35 edit main README.md 2024-08-21 18:36:25 +02:00
Soufiane Fariss
9959eb6bae web: edit explorer README 2024-08-21 18:33:58 +02:00
Fariss
c3f24c2f48 Merge pull request #2301 from s-ff/use-gzipped-preview
web: don't bundle preview data in build and release
2024-08-21 18:06:28 +02:00
Fariss
2c41d3ce89 Merge branch 'master' into use-gzipped-preview 2024-08-21 18:05:05 +02:00
Fariss
980814f7df update code comment
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2024-08-21 16:24:58 +02:00
Soufiane Fariss
6049062173 fix: typo Preview Dynamic 2024-08-21 11:05:21 +02:00
Soufiane Fariss
05083cfb6e refactor and optimize parseRules routine 2024-08-21 11:05:21 +02:00
Soufiane Fariss
0bdfb37287 use monospace font for match location nodes 2024-08-21 11:05:21 +02:00
Soufiane Fariss
5f5393af69 dynamic: disable process column and collapse pid into process 2024-08-21 11:05:21 +02:00
Soufiane Fariss
5c1c1b0ba9 remove default option scrollable and minify text 2024-08-21 11:05:21 +02:00
Soufiane Fariss
8fd90883b4 web: refactor and add support for laoding remote .gz using rdoc query param 2024-08-21 11:05:21 +02:00
Willi Ballenthin
22d20ed2b8 web: add umami script for collecting metrics (#2308) 2024-08-20 22:53:01 +02:00
Moritz
b3dd76adff Merge branch 'master' into use-gzipped-preview 2024-08-20 20:25:29 +02:00
Yacine
f6b7582606 bump to v7.2.0 (#2297)
* update CHANGELOG.md and version.py

---------

Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2024-08-20 20:12:46 +02:00
Yacine
791f5e2359 Add the ability to select which functions or processes you which to extract capabilities from (#2156) 2024-08-20 14:09:46 +02:00
Soufiane Fariss
c4c35e914d fix lint 2024-08-19 17:19:57 +02:00
Soufiane Fariss
1593779d6b use preview buttons as redirect to static URLs 2024-08-19 17:17:07 +02:00
Soufiane Fariss
5c6faaefff relax version down to 6.1.0 2024-08-19 17:17:07 +02:00
Soufiane Fariss
864cd77f9f remove loading function for preview data 2024-08-19 17:17:07 +02:00
Soufiane Fariss
164e075ca9 fix preview data placeholders 2024-08-19 15:51:06 +02:00
Soufiane Fariss
7592cfe268 don't include preview files in bundle mode 2024-08-19 15:22:12 +02:00
Soufiane Fariss
6a2039e7a6 bump max matches in dynamic mode to 25 matches per rule 2024-08-19 15:21:38 +02:00
Soufiane Fariss
0e4872507d process gzipped files 2024-08-19 15:20:27 +02:00
Soufiane Fariss
dd6cb4acc3 declare gzip files as static assets in vite.config.js 2024-08-19 15:19:30 +02:00
Soufiane Fariss
7e766048fa remove preview buttons in release mode 2024-08-19 15:13:02 +02:00
Soufiane Fariss
7c26490caa remove download button in release mode 2024-08-19 15:12:33 +02:00
Moritz
c409b2b7ed Merge pull request #2300 from s-ff/add-file-scope-rules 2024-08-17 09:09:08 +02:00
Yacine
6ff08aeeaf Merge branch 'master' into vmray-extractor 2024-08-17 02:15:01 +01:00
Soufiane Fariss
4501955728 remove octal repr for hex values 2024-08-16 23:37:30 +02:00
Capa Bot
6b4591de14 Sync capa rules submodule 2024-08-16 18:57:36 +00:00
Soufiane Fariss
00cce585d6 remove sorting from columns 2024-08-16 18:52:53 +02:00
Soufiane Fariss
19e2097f79 change placeholder text 2024-08-16 18:52:02 +02:00
Soufiane Fariss
b67bd4d084 add file-level rules to capabilities by function 2024-08-16 18:23:44 +02:00
Soufiane Fariss
854759cb43 add tooltip to show decimal/octal rep 2024-08-16 18:17:34 +02:00
Moritz
348e0b3203 Merge pull request #2299 from s-ff/issue/2236
web: add copy rule name and description to VT to right click menu
2024-08-16 17:21:31 +02:00
Soufiane Fariss
03e2195582 add copy rule name and description to VT 2024-08-16 16:49:51 +02:00
Capa Bot
076bb13e2d Sync capa rules submodule 2024-08-16 14:05:19 +00:00
Moritz
76bd1460ba Merge pull request #2298 from s-ff/fixes-2288-2289-2290
web: fix global search and add UI tweaks
2024-08-16 15:02:59 +02:00
Capa Bot
14a7bab890 Sync capa rules submodule 2024-08-16 12:18:34 +00:00
Soufiane Fariss
8ca88d94d5 disable show lib rules button if none 2024-08-16 14:14:29 +02:00
Capa Bot
9d3f732b33 Sync capa rules submodule 2024-08-16 11:25:22 +00:00
Soufiane Fariss
d3e3c966d6 web: introduce column filters and UI tweaks 2024-08-16 12:57:44 +02:00
Capa Bot
e402aab41d Sync capa-testfiles submodule 2024-08-15 20:03:31 +00:00
Soufiane Fariss
c73abb8855 add 'distinct' keyword to clarify count is distinct 2024-08-15 17:05:47 +02:00
Soufiane Fariss
04071606cd fix global search in shhow capabilities by function 2024-08-15 17:03:02 +02:00
Moritz
19698b1ba1 Merge pull request #2296 from s-ff/rearrange-navbar-icons
rearrange navbar icons
2024-08-15 16:58:31 +02:00
Soufiane Fariss
25e9e18097 rearrange navbar icons
moves FLARE logo to the right left side, and make a link to /
2024-08-15 16:48:54 +02:00
Moritz
3a21648e78 Merge pull request #2294 from s-ff/render-results-in-analysis
web: diplay results in new /analysis route
2024-08-15 16:28:20 +02:00
Soufiane Fariss
8dcb7a473e web: diplay results in new /analysis route 2024-08-15 16:10:41 +02:00
Capa Bot
cf91503dc3 Sync capa rules submodule 2024-08-15 12:33:40 +00:00
Moritz
d8691edd15 Merge pull request #2282 from mandiant/dependabot/pip/types-psutil-6.0.0.20240621
build(deps): bump types-psutil from 5.8.23 to 6.0.0.20240621
2024-08-15 14:30:57 +02:00
Moritz
56a6f9c83e Merge pull request #2281 from mandiant/dependabot/pip/pip-24.2
build(deps): bump pip from 24.1.2 to 24.2
2024-08-15 11:40:59 +02:00
Moritz
e25e68e169 Merge pull request #2280 from mandiant/dependabot/pip/black-24.8.0
build(deps): bump black from 24.4.2 to 24.8.0
2024-08-15 11:40:41 +02:00
dependabot[bot]
728742a1ad build(deps): bump types-psutil from 5.8.23 to 6.0.0.20240621
Bumps [types-psutil](https://github.com/python/typeshed) from 5.8.23 to 6.0.0.20240621.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-psutil
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-15 09:31:06 +00:00
Moritz
da273824d1 Merge pull request #2279 from mandiant/dependabot/pip/pyinstaller-6.10.0
build(deps): bump pyinstaller from 6.9.0 to 6.10.0
2024-08-15 11:30:05 +02:00
Moritz
7a6f63cf2b Merge pull request #2278 from mandiant/dependabot/pip/types-requests-2.32.0.20240712
build(deps): bump types-requests from 2.32.0.20240602 to 2.32.0.20240712
2024-08-15 11:29:52 +02:00
Capa Bot
d62734ecc2 Sync capa-testfiles submodule 2024-08-14 12:20:36 +00:00
Capa Bot
5ccb642929 Sync capa rules submodule 2024-08-14 08:48:33 +00:00
Moritz
8d5fcdf287 Merge pull request #2201 from Ana06/ida_apis
ida extractor: extract APIs from renamed globals
2024-08-13 17:59:11 +02:00
Ana Maria Martinez Gomez
be8499238c ida extractor: extract APIs from renamed globals
Add support to extract dynamically resolved APIs stored in global
variables that have been renamed (for example using the `renimp.idc`
script included with IDA Pro).
2024-08-13 17:15:14 +02:00
Capa Bot
40c7714c48 Sync capa-testfiles submodule 2024-08-13 14:59:22 +00:00
Capa Bot
460590cec0 Sync capa-testfiles submodule 2024-08-13 14:59:00 +00:00
Capa Bot
25d2ef30e7 Sync capa-testfiles submodule 2024-08-13 14:58:53 +00:00
Moritz
71ae51ef69 Merge pull request #2284 from s-ff/move-release-to-public
use relative path for zip release asset
2024-08-12 17:45:51 +02:00
Soufiane Fariss
216bfb968d fix typo, and move release asset to public dir
This commit -
- fixes a a typo in package.json (outDir)
- sets the href of the zip file to ./
- moves the zip asset to the public dir.

Note: public dir is a special dir which hosts files that would be served
as is, so it makes sense to put the release for download there.
2024-08-12 17:26:50 +02:00
dependabot[bot]
32cb0365f8 build(deps): bump pip from 24.1.2 to 24.2
Bumps [pip](https://github.com/pypa/pip) from 24.1.2 to 24.2.
- [Changelog](https://github.com/pypa/pip/blob/main/NEWS.rst)
- [Commits](https://github.com/pypa/pip/compare/24.1.2...24.2)

---
updated-dependencies:
- dependency-name: pip
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-12 14:33:31 +00:00
dependabot[bot]
b299e4bc1f build(deps): bump black from 24.4.2 to 24.8.0
Bumps [black](https://github.com/psf/black) from 24.4.2 to 24.8.0.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/24.4.2...24.8.0)

---
updated-dependencies:
- dependency-name: black
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-12 14:33:26 +00:00
dependabot[bot]
bc2802fd72 build(deps): bump pyinstaller from 6.9.0 to 6.10.0
Bumps [pyinstaller](https://github.com/pyinstaller/pyinstaller) from 6.9.0 to 6.10.0.
- [Release notes](https://github.com/pyinstaller/pyinstaller/releases)
- [Changelog](https://github.com/pyinstaller/pyinstaller/blob/develop/doc/CHANGES.rst)
- [Commits](https://github.com/pyinstaller/pyinstaller/compare/v6.9.0...v6.10.0)

---
updated-dependencies:
- dependency-name: pyinstaller
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-12 14:33:21 +00:00
dependabot[bot]
81a14838bd build(deps): bump types-requests from 2.32.0.20240602 to 2.32.0.20240712
Bumps [types-requests](https://github.com/python/typeshed) from 2.32.0.20240602 to 2.32.0.20240712.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-requests
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-12 14:33:17 +00:00
Fariss
1c9a86ca20 Merge pull request #2275 from s-ff/add-download-button
web: add button to download release
2024-08-12 16:03:47 +02:00
Fariss
32fefa60cc Merge branch 'master' into add-download-button 2024-08-12 15:25:41 +02:00
Soufiane Fariss
09bbe80dfb add download button to download release 2024-08-12 14:52:14 +02:00
Moritz
239ad4a17e reorganize and extend readme (#2258)
* reorganize and extend readme
2024-08-12 12:50:19 +02:00
Fariss
ab3b074c6a Skip build checks on documentation updates (#2271)
* skip build checks on documentation updates
2024-08-12 12:26:14 +02:00
Fariss
e863ce5ff3 web: fix build warnings (#2268)
* web: remove unneeded import

* web: fix code comments style
2024-08-12 10:39:33 +02:00
Willi Ballenthin
8e4c0e3040 web: separate build and deploy, incorporate landing page (#2264) 2024-08-10 11:57:50 +02:00
Fariss
401a0ee0ff web: change base url for capa Explorer Web (#2267) 2024-08-09 22:02:21 +02:00
Fariss
f69fabc2b0 add path exclusions to python tests.yml workflow (#2263)
* add path exclusions to tests.yml

* changelog: ci: add exclusions to tests.yml

* changelog: update entry

* update exclusion list in tests.yml
2024-08-09 16:12:08 +02:00
Moritz
c0a7f765c5 Merge branch 'master' into vmray-extractor 2024-08-09 13:58:45 +02:00
lakshay
87f691677c #2119 issue: use bytes.fromhex instead of binascii (#2235)
* #2119 issue: use bytes.fromhex instead of binascii

---------

Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2024-08-09 12:25:25 +02:00
Moritz
ea9853e667 Merge pull request #2224 from s-ff/webui
initial release of Capa Explorer Web
2024-08-09 10:06:26 +02:00
Willi Ballenthin
312dd0d40f Merge branch 'master' into webui 2024-08-08 13:49:35 +02:00
Willi Ballenthin
44cbe664e4 Update web/explorer/README.md 2024-08-08 13:06:04 +02:00
Willi Ballenthin
6b8e2b3e81 Update web/explorer/README.md 2024-08-08 13:05:57 +02:00
Willi Ballenthin
ba9ab7c876 Update web/explorer/DEVELOPMENT.md 2024-08-08 13:05:48 +02:00
Soufiane Fariss
1af97f6681 update web workflow Format steps to use npm run format:check 2024-08-08 10:33:07 +02:00
Soufiane Fariss
05575e1e92 encore rdoc query paramter URL 2024-08-08 09:10:47 +02:00
Soufiane Fariss
9d137a207f replace path traversal with @ path shortcut 2024-08-08 08:57:37 +02:00
Soufiane Fariss
850ae5a916 tests: update unit tests after refactoring parseFunctionCapabilities 2024-08-08 08:42:45 +02:00
Soufiane Fariss
e8054c277d add deploy and tests workflows 2024-08-08 08:14:47 +02:00
Soufiane Fariss
e8ea461456 fix formatting 2024-08-08 08:12:50 +02:00
Soufiane Fariss
bb8991af8e fix formatting 2024-08-08 08:10:32 +02:00
Soufiane Fariss
368f635387 minor fixes: NamespaceChart and ProcessCapabilities 2024-08-08 07:18:56 +02:00
Soufiane Fariss
287e4282a9 set web-app version to 1.0.0 2024-08-08 07:18:16 +02:00
Soufiane Fariss
1f6ce48e40 refactor RuleMatchesTable
This commit:
- add two new base CSS utility classes
- stores the results of parsing in sessionStorage for reuse
- add a new settings option `Show column filters`
- replaces ../../../ with a path shortcut
2024-08-08 07:13:42 +02:00
Soufiane Fariss
7cb31cf23c refactor: add new URL creation helper functions for VT and capa-rules 2024-08-08 07:11:31 +02:00
Soufiane Fariss
01e6619182 update Import Analysis url 2024-08-08 07:10:49 +02:00
Soufiane Fariss
20d7bf1402 gitignore: update gitignore 2024-08-08 07:09:24 +02:00
Soufiane Fariss
6b8983c0c4 simplify function capabilities 2024-08-08 07:08:46 +02:00
Soufiane Fariss
97bd4992b1 add path resolving shortcuts "@" 2024-08-08 07:06:41 +02:00
Soufiane Fariss
843fd34737 changelog: update entry 2024-08-08 07:05:24 +02:00
Soufiane Fariss
dfc19d8cb2 Update docuemntation 2024-08-08 07:04:17 +02:00
Capa Bot
1564f24330 Sync capa rules submodule 2024-08-07 09:31:59 +00:00
Capa Bot
0d87bb0504 Sync capa-testfiles submodule 2024-08-07 08:51:38 +00:00
Soufiane Fariss
db423d9b0a add comments to rule column components 2024-08-06 18:11:41 +02:00
Soufiane Fariss
ebfba543e6 fix Metadata panel on smaller screens 2024-08-06 17:11:43 +02:00
dependabot[bot]
46c464282e build(deps): bump ruff from 0.5.2 to 0.5.6 (#2253)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.5.2 to 0.5.6.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.5.2...0.5.6)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-06 09:38:52 +02:00
dependabot[bot]
aa225dac5c build(deps): bump mypy from 1.10.0 to 1.11.1 (#2254)
Bumps [mypy](https://github.com/python/mypy) from 1.10.0 to 1.11.1.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.10.0...v1.11.1)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-06 09:38:31 +02:00
dependabot[bot]
c2376eaf7b build(deps): bump tqdm from 4.66.4 to 4.66.5 (#2252)
Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.66.4 to 4.66.5.
- [Release notes](https://github.com/tqdm/tqdm/releases)
- [Commits](https://github.com/tqdm/tqdm/compare/v4.66.4...v4.66.5)

---
updated-dependencies:
- dependency-name: tqdm
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-06 09:38:10 +02:00
dependabot[bot]
6451fa433b build(deps): bump protobuf from 5.27.1 to 5.27.3 (#2255)
Bumps [protobuf](https://github.com/protocolbuffers/protobuf) from 5.27.1 to 5.27.3.
- [Release notes](https://github.com/protocolbuffers/protobuf/releases)
- [Changelog](https://github.com/protocolbuffers/protobuf/blob/main/protobuf_release.bzl)
- [Commits](https://github.com/protocolbuffers/protobuf/compare/v5.27.1...v5.27.3)

---
updated-dependencies:
- dependency-name: protobuf
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-06 09:38:01 +02:00
Soufiane Fariss
765c7cb792 add on pull_request trigger to deploy-webui.yml 2024-08-05 19:51:55 +02:00
Soufiane Fariss
b675c9a77c change target branch to master in deploy-webui.yml 2024-08-05 19:37:32 +02:00
Fariss
ac081336ba Merge branch 'master' into webui 2024-08-05 16:01:41 +02:00
Soufiane Fariss
a15eb835f4 format code 2024-08-05 15:50:48 +02:00
Soufiane Fariss
fcdaabf34e update development links 2024-08-05 15:49:00 +02:00
Soufiane Fariss
283aa27152 add DEVELOPMENT.md 2024-08-05 15:47:03 +02:00
Soufiane Fariss
f856ea7454 modify deploy-webui.yml workflow to user web/explorer 2024-08-05 15:33:22 +02:00
nocontribute
ebb778ae0d delete webui/package-lock.json 2024-08-05 15:21:51 +02:00
Soufiane Fariss
e9e5d2bb12 delete webui 2024-08-05 15:20:40 +02:00
nocontribute
bb1ef6ca56 move package-lock.json to web/explorer 2024-08-05 15:17:47 +02:00
Soufiane Fariss
7e64306f1c move webui to web/explorer 2024-08-05 15:17:04 +02:00
Soufiane Fariss
6b19e7b372 add README.md for Capa Explorer WebUI 2024-08-05 15:10:38 +02:00
Soufiane Fariss
bb60099ab6 rename window title to 'Capa Explorer' 2024-08-05 14:43:07 +02:00
Soufiane Fariss
d609203fcd add 404 page 2024-08-05 14:16:06 +02:00
Soufiane Fariss
fcf200f13f fix metadata panel on small screens 2024-08-05 14:05:46 +02:00
Soufiane Fariss
7cb93c8ebd update .prettierrc.json, and reformat code 2024-08-05 13:31:45 +02:00
Soufiane Fariss
eb69b383a4 move url creation function to util/urlHelpers.js 2024-08-05 13:19:48 +02:00
Capa Bot
04d127f69f Sync capa rules submodule 2024-08-05 09:37:13 +00:00
Capa Bot
9dd39926d7 Sync capa-testfiles submodule 2024-08-05 09:36:34 +00:00
Capa Bot
13d14f6cb6 Sync capa rules submodule 2024-08-02 13:05:03 +00:00
Capa Bot
260da8ed2c Sync capa rules submodule 2024-08-02 13:02:28 +00:00
Soufiane Fariss
a6884db1d3 fix: add lint and test steps to deploy workflow 2024-08-02 02:01:38 +02:00
Soufiane Fariss
67d3916c41 add lint and test steps to deploy workflow 2024-08-02 01:57:58 +02:00
Soufiane Fariss
b0ffc86399 fix lint error 2024-08-02 01:48:51 +02:00
Soufiane Fariss
07b4e1f8a2 implement unit test 2024-08-02 01:26:36 +02:00
Soufiane Fariss
4137923c2e dynamic: revert to showing 1 match per rule 2024-08-01 21:53:08 +02:00
Soufiane Fariss
33be4d1f8e dynamic: only show first 20 matches per rule 2024-08-01 21:45:59 +02:00
Soufiane Fariss
8e9eadf98a feature: support gzipped rdoc
For dynamic mode, even if the rdoc is gzipped, parsing it can result in
a big performance hit. For example if a user load a 1MB gzipped archive,
which then decompresses into a >70MB JSON object, this can result in
slower parsing. We need to think about how to streamline large rdocs.

This commit adds a restriction on the number of matches to show in
dynamic mode (maxMatches = 1)
2024-08-01 21:31:38 +02:00
Soufiane Fariss
9107819cf1 fix: force reload page on import analysis 2024-08-01 19:13:23 +02:00
Soufiane Fariss
b74738adcf feature: show namespace chart 2024-08-01 18:56:41 +02:00
Soufiane Fariss
b229048b51 feature: show namespace chart 2024-08-01 18:45:22 +02:00
Moritz
afb72867f4 assert sample analysis data is present 2024-08-01 07:58:29 +02:00
Soufiane Fariss
4fe7f784e9 edit code comments 2024-08-01 00:50:14 +02:00
Soufiane Fariss
b7b8792f70 Force reload the page on Import Analysis 2024-07-31 21:24:38 +02:00
mr-tz
e47635455e add dynamic vmray feature tests 2024-07-31 13:30:30 +00:00
mr-tz
e83f289c8e add script to minimize vmray archive to only relevant files 2024-07-31 13:28:41 +00:00
mr-tz
3982356945 load gzipped rd, see capa-testfiles#245 2024-07-31 12:59:16 +00:00
lakshay
e637e5a09e #2244 Issue: Update deprecated ruff linter settings (#2248) 2024-07-31 10:28:52 +02:00
Soufiane Fariss
0ea6f1e270 fix: do not toggle/on feature and statements 2024-07-31 00:55:18 +02:00
Soufiane Fariss
f6bc42540c if node already expanded, toggle it off 2024-07-31 00:07:40 +02:00
Mike Hunhoff
a8d849e872 vmray: improve comments models.py 2024-07-30 11:43:53 -06:00
Soufiane Fariss
62701a2837 use Hash-Based routing (#) 2024-07-30 17:38:40 +02:00
Soufiane Fariss
f60e3fc531 lints 2024-07-30 17:38:08 +02:00
Soufiane Fariss
b6f0ee539b wip: only include process name in api call details 2024-07-30 17:03:30 +02:00
Soufiane Fariss
e70e1b0641 feature: add call information to api feature in dynamic mode (-vv) 2024-07-30 16:24:05 +02:00
Mike Hunhoff
71c515d4d7 vmray: improve comments __init__.py 2024-07-29 12:19:53 -06:00
Mike Hunhoff
139dcc430c vmray: improve logging 2024-07-29 12:16:05 -06:00
Mike Hunhoff
7bf0b396ee core: improve error message for vmray 2024-07-29 12:02:14 -06:00
Mike Hunhoff
87dfa50996 scripts: remove old code from show-features.py 2024-07-29 12:00:29 -06:00
Mike Hunhoff
8cba23bbce vmray: improve extract_import_names 2024-07-29 11:49:04 -06:00
Mike Hunhoff
1a3cf4aa8e vmray: update extractor.py format_params 2024-07-29 11:41:31 -06:00
Mike Hunhoff
51b853de59 vmray: remove bad print statements 2024-07-29 11:39:03 -06:00
Mike Hunhoff
3043fd6ac8 vmray: merge upstream 2024-07-29 11:37:37 -06:00
Moritz
b9c4cc681b Merge pull request #2238 from s-ff/scripts-fix-caps-by-function
scripts/show-capabilities-by-function.py: fix incorrect function address
2024-07-29 17:42:30 +02:00
Soufiane Fariss
13261d0c41 include basic block matches in capabilities by function table 2024-07-29 17:02:54 +02:00
Soufiane Fariss
8476aeee35 scripts/show-capabilities-by-function.py: fix incorrect function address 2024-07-29 14:17:40 +02:00
Soufiane Fariss
38cf1f1041 feature: show regex captures 2024-07-29 03:56:13 +02:00
Soufiane Fariss
d81b123e97 feature: add right click links context menu 2024-07-28 23:25:47 +02:00
Soufiane Fariss
029259b8ed make rule names and matches click event expand the node 2024-07-28 19:56:33 +02:00
Soufiane Fariss
e3f695b947 bump upload size limit to 100MB 2024-07-26 11:46:31 +02:00
Soufiane Fariss
d25c86c08b reformat function capabilities into a rowspan table instead of tree table 2024-07-26 03:21:15 +02:00
Mike Hunhoff
b967213302 vmray: improve comments __init__.py 2024-07-25 12:30:20 -06:00
Mike Hunhoff
05fb8f658f vmray: fix flake8 lints 2024-07-25 12:19:22 -06:00
Mike Hunhoff
7b3812ae19 vmray: improve error reporting 2024-07-25 12:12:49 -06:00
Mike Hunhoff
5b7a2be652 vmray: remove outdated comments __init__.py 2024-07-25 09:33:17 -06:00
Soufiane Fariss
4aad53c5b3 feature: implement parent-child process tree 2024-07-24 19:24:39 +02:00
Mike Hunhoff
b8d3d77829 vmray: document vmray support in README 2024-07-24 10:35:34 -06:00
Mike Hunhoff
9a1364c21c vmray: document vmray support in README 2024-07-24 10:32:22 -06:00
Mike Hunhoff
6e146bb126 vmray: fix lints 2024-07-24 10:12:21 -06:00
Mike Hunhoff
85373a7ddb cape: add explicit check for CAPE report format file extension 2024-07-24 10:09:22 -06:00
Mike Hunhoff
f6d12bcb41 vmray: fix lints 2024-07-24 10:03:57 -06:00
Mike Hunhoff
f471386456 vmray: merge upstream and fix conflicts 2024-07-24 10:02:07 -06:00
Soufiane Fariss
0028da5270 implement text truncation for process names 2024-07-24 14:30:35 +02:00
Yacine
cf3494d427 Add a Feature Extractor for the Drakvuf Sandbox (#2143)
* initial commit

* update changelog

* Update CHANGELOG.md

* Update pyproject.toml

* Apply suggestions from code review: Typos

Co-authored-by: Vasco Schiavo <115561717+VascoSch92@users.noreply.github.com>

* capa/helpers.py: update if/else statement

Co-authored-by: Vasco Schiavo <115561717+VascoSch92@users.noreply.github.com>

* loader.py: replace print() statement with log.info()

* Update capa/features/extractors/drakvuf/models.py

Co-authored-by: Moritz <mr-tz@users.noreply.github.com>

* extractors/drakvuf/call.py: yield arguments right to left

* extractors/drakvuf/file.py: add a TODO comment for extracting more file features

* extractors/drakvuf/global_.py: add arch extraction

* extractors/drakvuf/helpers.py: ignore null pids

* capa/helpers.py: mention msgspec.json explicitely

* capa/helpers.py: generalize empty sandbox reports error logging

* capa/loader.py: log jsonl garbage collection into debug

* features/extractors/drakvuf/models.py: add documentation for SystemCall class

* capa/main.py: fix erroneous imports

* drakvuf extractor: fixed faulty type annotations

* fix black formatting

* fix flake8 issues

* drakvuf file extraction: add link to tracking issue

* drakvuf reports: add the ability to read gzip-compressed report files

* capa/helpers.py: fix mypy issues

* apply review comments

* drakvuf/helpers.py: add more information about null pid

* drakvuf/file.py: remove discovered_dlls file strings extraction

* capa/helpers.py: add comments for the dynamic extensions

* capa/helpers.py: log bad lines

* capa/helpers.py: add gzip support for reading one jsonl line

* drakvuf/helpers.py: add comment for sort_calls()

* tests/fixtures.py: add TODO for unifying CAPE and Drakvuf tests

* drakvuf/models.py: add TODO comment for supporting more drakvuf plugins

* tests/fixtures.py: remove obsolete file strings tests

* Update capa/main.py

Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>

* Update capa/features/extractors/drakvuf/models.py

Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>

* Update capa/features/extractors/drakvuf/models.py

Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>

* Update capa/features/extractors/drakvuf/call.py

Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>

* Update CHANGELOG.md

Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>

* Update capa/features/extractors/drakvuf/helpers.py

Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>

* review comments

* Update capa/features/extractors/drakvuf/extractor.py

Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>

* Update capa/features/extractors/drakvuf/models.py

Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>

* styling

* drakvuf/extractor.py: black linting

* drakvuf/models.py: remove need to empty report checking

* tests: add drakvuf models test

* Update capa/features/extractors/drakvuf/global_.py

Co-authored-by: msm-cert <156842376+msm-cert@users.noreply.github.com>

* Update tests/test_cape_features.py

Co-authored-by: msm-cert <156842376+msm-cert@users.noreply.github.com>

* Update capa/features/extractors/drakvuf/models.py

Co-authored-by: msm-cert <156842376+msm-cert@users.noreply.github.com>

* Apply suggestions from code review: rename Drakvuf to DRAKVUF

Co-authored-by: msm-cert <156842376+msm-cert@users.noreply.github.com>

* drakvuf/call.py: use int(..., 0) instead of str_to_number()

* remove str_to_number

* drakvuf/call.py: yield argument memory address value as well

* Update call.py: remove verbosity in yield statement

* Update call.py: yield missing address as well

* drakvuf/call.py: yield entire argument string only

* update readme.md

* Update README.md: typo

* Update CHANGELOG.md

Co-authored-by: msm-cert <156842376+msm-cert@users.noreply.github.com>

---------

Co-authored-by: Vasco Schiavo <115561717+VascoSch92@users.noreply.github.com>
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
Co-authored-by: msm-cert <156842376+msm-cert@users.noreply.github.com>
2024-07-24 14:22:21 +02:00
Soufiane Fariss
3f33b82ace changelog: add webui 2024-07-24 12:49:26 +02:00
Soufiane Fariss
12f1851ba5 deploy-webui.yml: include submodule capa-rules checkout 2024-07-24 12:41:45 +02:00
Soufiane Fariss
6da0e5d985 highlight links, use monospace for feature values 2024-07-24 11:31:39 +02:00
Willi Ballenthin
e2e84f7f50 ELF: better handle corrupt files (#2227)
such as when there's a missing symbol table and invalid relocation table.
and then handle when Viv fails to load a workspace.

closes #2226
2024-07-24 09:22:30 +02:00
Soufiane Fariss
106c31735e link sha256 to VT external link 2024-07-23 23:30:06 +02:00
Soufiane Fariss
277e9d1551 remove Toolset dropdown menu 2024-07-23 23:16:13 +02:00
Soufiane Fariss
9db01e340c add href links to MBC, and refactor into helpers functions
Create href for both MBC and ATT&CK using helper functions
`createMBCHref` and `createATTACKHref`
2024-07-23 23:01:12 +02:00
Soufiane Fariss
626ea51c20 use existings tests/data/rd rdocs for Preview
Instead of duplicating JSON files used for preview by including
them in src/assets/<rdoc>.json, let's re-use the existing
tests/data/rd from submodule capa-testfiles.
2024-07-23 22:57:00 +02:00
Mike Hunhoff
31e53fab20 vmray: improve models.py comments 2024-07-23 09:52:36 -06:00
Mike Hunhoff
cbdc7446aa vmray: merge upstream 2024-07-23 09:49:40 -06:00
Mike Hunhoff
46b68d11b7 vmray: improve models.py comments 2024-07-23 09:48:52 -06:00
dependabot[bot]
fd686ac591 build(deps): bump types-protobuf from 5.26.0.20240422 to 5.27.0.20240626 (#2185)
Bumps [types-protobuf](https://github.com/python/typeshed) from 5.26.0.20240422 to 5.27.0.20240626.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-protobuf
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-23 09:17:45 +02:00
dependabot[bot]
17aab2c4fc build(deps): bump pip from 24.0 to 24.1.2 (#2199)
Bumps [pip](https://github.com/pypa/pip) from 24.0 to 24.1.2.
- [Changelog](https://github.com/pypa/pip/blob/main/NEWS.rst)
- [Commits](https://github.com/pypa/pip/commits)

---
updated-dependencies:
- dependency-name: pip
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-23 09:16:40 +02:00
dependabot[bot]
216ac8dd96 build(deps): bump deptry from 0.16.1 to 0.17.0 (#2222)
Bumps [deptry](https://github.com/fpgmaas/deptry) from 0.16.1 to 0.17.0.
- [Release notes](https://github.com/fpgmaas/deptry/releases)
- [Changelog](https://github.com/fpgmaas/deptry/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fpgmaas/deptry/compare/0.16.1...0.17.0)

---
updated-dependencies:
- dependency-name: deptry
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-23 09:16:22 +02:00
dependabot[bot]
d68e057439 build(deps): bump pyinstaller from 6.8.0 to 6.9.0 (#2220)
Bumps [pyinstaller](https://github.com/pyinstaller/pyinstaller) from 6.8.0 to 6.9.0.
- [Release notes](https://github.com/pyinstaller/pyinstaller/releases)
- [Changelog](https://github.com/pyinstaller/pyinstaller/blob/develop/doc/CHANGES.rst)
- [Commits](https://github.com/pyinstaller/pyinstaller/compare/v6.8.0...v6.9.0)

---
updated-dependencies:
- dependency-name: pyinstaller
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-23 09:16:05 +02:00
Soufiane Fariss
3c2749734c Remove demo rdoc json files (static and dynamic) 2024-07-23 01:14:31 +02:00
Soufiane Fariss
5c60efa81f add Github Pages deployment workflow 2024-07-23 00:26:24 +02:00
nocontribute
09d86245e5 add package-lock.json cache 2024-07-23 00:26:24 +02:00
Soufiane Fariss
2862cb35c2 remove Github Pages workflow from webui branch 2024-07-23 00:26:24 +02:00
Soufiane Fariss
c3aa306d6c add Github Pages deployement workflow 2024-07-23 00:26:24 +02:00
Soufiane Fariss
6bec5d40bd webui: initial release 2024-07-23 00:26:24 +02:00
Mike Hunhoff
3b94961133 vmray: complete pefile model tests 2024-07-19 15:50:07 -06:00
Mike Hunhoff
6ef485f67b vmray: refactor model tests 2024-07-19 15:44:53 -06:00
Mike Hunhoff
4dfc53a58f vmray: refactor model tests 2024-07-19 15:42:04 -06:00
Mike Hunhoff
98939f8a8f vmray: improve FunctionCall model 2024-07-19 15:38:26 -06:00
Mike Hunhoff
4490097e11 vmray: add summary_v2.json model tests 2024-07-19 15:28:47 -06:00
Mike Hunhoff
2ba2a2b013 vmray: remove unneeded json.loads from __init__.py 2024-07-19 15:05:21 -06:00
Mike Hunhoff
28792ec6a6 vmray: add model tests for FunctionCall 2024-07-19 13:56:46 -06:00
Mike Hunhoff
658927c103 vmray: refactor models.py 2024-07-19 11:58:48 -06:00
Mike Hunhoff
673f7cccfc vmray: refactor models.py 2024-07-19 11:57:07 -06:00
Mike Hunhoff
6e0dc83451 vmray: refactor global_.py 2024-07-19 11:51:16 -06:00
xusheng
da6c6cfb48 Update Binary Ninja version to 4.1 and use Python 3.9 to test it (#2212) 2024-07-19 02:28:10 +02:00
Mike Hunhoff
8bf0d16fd8 vmray: add init support for ELF files 2024-07-18 17:52:33 -06:00
Mike Hunhoff
24a31a8bc3 vmray: add comments to __init__.py 2024-07-18 14:23:20 -06:00
Mike Hunhoff
6f7cc7cdb0 vmray: improve detections for unsupported input files 2024-07-18 11:33:42 -06:00
Mike Hunhoff
64a09d3146 vmray: remove broken assert for unique OS PIDs 2024-07-18 11:20:03 -06:00
Mike Hunhoff
998537ddf8 vmray: remove outdated comments 2024-07-18 09:10:50 -06:00
Mike Hunhoff
5afea29473 vmray: update CHANGELOG release notes with VMRay integration 2024-07-18 09:06:58 -06:00
Mike Hunhoff
fd7bd94b48 vmray: remove outdated comments 2024-07-18 08:50:20 -06:00
Mike Hunhoff
330c77a32a vmray: implement get_call_name 2024-07-17 15:04:00 -06:00
Mike Hunhoff
19a6f3ad49 vmray: improve supported file type validation 2024-07-17 12:37:51 -06:00
Mike Hunhoff
100df45cc0 vmray: add logging for skipped deref param types 2024-07-17 12:27:14 -06:00
Mike Hunhoff
cc87ef39d5 vmray: remove and document extract_call_features comments 2024-07-17 12:18:01 -06:00
Mike Hunhoff
ec7e43193e vmray: update comment for extract_process_features 2024-07-17 12:10:18 -06:00
Mike Hunhoff
b68a91e10b vmray: validate supported flog version 2024-07-17 12:06:23 -06:00
Mike Hunhoff
15889749c0 vmray: merge upstream 2024-07-17 11:54:58 -06:00
dependabot[bot]
9353e46615 build(deps): bump ruff from 0.5.0 to 0.5.2 (#2209)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.5.0 to 0.5.2.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.5.0...0.5.2)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-17 02:23:19 +02:00
Mike Hunhoff
af26bef611 vmray: fix lints 2024-07-12 20:21:57 -06:00
Mike Hunhoff
42fddfbf31 vmray: improve comments 2024-07-12 20:19:06 -06:00
Mike Hunhoff
5214675eeb vmray: update tests.yml 2024-07-12 19:55:06 -06:00
Mike Hunhoff
4f2467cae0 vmray: update CHANGELOG 2024-07-12 19:53:33 -06:00
Mike Hunhoff
28c278b9e6 vmray: improve comments 2024-07-12 19:09:10 -06:00
Mike Hunhoff
26b5870ef4 vmray: improve comments 2024-07-12 19:06:06 -06:00
Mike Hunhoff
1f5b6ec52c vmray: improve comments 2024-07-12 19:00:48 -06:00
Mike Hunhoff
307b0cc327 vmray: add comments 2024-07-12 18:51:21 -06:00
Mike Hunhoff
253d70efac vmray: add comments 2024-07-12 18:49:08 -06:00
Mike Hunhoff
85632f698f vmray: clean up models 2024-07-12 18:45:53 -06:00
Mike Hunhoff
931a9b9421 vmray: clean up models 2024-07-12 18:44:29 -06:00
Mike Hunhoff
06631fc39d vmray: remove call feature extraction for out parameters 2024-07-12 18:42:42 -06:00
Mike Hunhoff
4bbe9e1ce9 vmray: emit number and string call features for pointer dereference 2024-07-12 18:35:50 -06:00
Mike Hunhoff
e2f5eb7d30 vmray: clean up models 2024-07-12 16:43:48 -06:00
Mike Hunhoff
5b7a0cad5f vmray: emit number call features for output parameters 2024-07-12 16:36:28 -06:00
Mike Hunhoff
da0545780b vmray: emit number call features for input parameters 2024-07-12 16:25:56 -06:00
Mike Hunhoff
bcdaa80dfa vmray: emit file import features 2024-07-12 13:34:30 -06:00
Mike Hunhoff
aad4854a61 vmray: use process OS PID instead of monitor ID 2024-07-12 11:33:13 -06:00
Mike Hunhoff
cbf6ecbd4d Merge branch 'vmray-extractor' of github.com:mandiant/capa into vmray-extractor 2024-07-12 10:15:40 -06:00
Mike Hunhoff
81581fe85e vmray: emit string file featureS 2024-07-12 10:15:28 -06:00
Mike Hunhoff
194017bce3 vmray: merge upstream 2024-07-12 09:27:49 -06:00
Maxime Berthault
76913af20b Binary Ninja update and fix (#2205)
* Fix binja warning (use of a deprecated API method)

* Update binja plugin
> Fix json openning and parsing
> Fix base address

* Fix code_style

* lint black update
2024-07-12 12:25:19 +02:00
mr-tz
d1f6bb3a44 Merge branch 'master' into vmray-extractor 2024-07-03 06:49:43 +00:00
Moritz
bb86d1485c Merge pull request #2187 from mandiant/dependabot/pip/flake8-comprehensions-3.15.0
build(deps): bump flake8-comprehensions from 3.14.0 to 3.15.0
2024-07-02 11:14:26 +02:00
Moritz
cd3086cfa4 Merge pull request #2184 from mandiant/dependabot/pip/ruff-0.5.0
build(deps): bump ruff from 0.4.8 to 0.5.0
2024-07-02 11:14:11 +02:00
Capa Bot
120f34e8ef Sync capa-testfiles submodule 2024-07-02 07:56:15 +00:00
Ilyas Osman
5495a8555c Fix incomplete f-strings (#2188)
* Fix incomplete f-strings

* Fix incomplete f-strings

* Apply black formatting to fix linting errors

* Apply black formatting to fix linting errors
2024-07-02 09:53:41 +02:00
Moritz
1a447013bd Merge pull request #2182 from yelhamer/process-name-interface-show-features
scripts/show-features.py: use extractor.get_process_name() interface …
2024-07-02 09:48:17 +02:00
Yacine Elhamer
fccb533841 test/scripts.py: bugfix 2024-07-01 21:59:28 +01:00
Yacine Elhamer
3b165c3d8e test:scripts.py: add tests for show-features.py process filtering 2024-07-01 21:41:46 +01:00
dependabot[bot]
cd5199f873 build(deps): bump flake8-comprehensions from 3.14.0 to 3.15.0
Bumps [flake8-comprehensions](https://github.com/adamchainz/flake8-comprehensions) from 3.14.0 to 3.15.0.
- [Changelog](https://github.com/adamchainz/flake8-comprehensions/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/adamchainz/flake8-comprehensions/compare/3.14.0...3.15.0)

---
updated-dependencies:
- dependency-name: flake8-comprehensions
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-01 14:48:28 +00:00
dependabot[bot]
202b5ddae7 build(deps): bump ruff from 0.4.8 to 0.5.0
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.4.8 to 0.5.0.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.4.8...0.5.0)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-01 14:48:15 +00:00
Yacine Elhamer
0b70abca93 show-features.py: add other usage of get_process_name() 2024-07-01 12:03:12 +01:00
Yacine Elhamer
6de22a0264 show-features.py: fix process filtering bug 2024-07-01 10:34:19 +01:00
Yacine Elhamer
fd811d1387 scripts/show-features.py: use extractor.get_process_name() interface for getting process name 2024-07-01 09:55:24 +01:00
Moritz
b617179525 Merge pull request #2165 from mandiant/dependabot/pip/flake8-7.1.0
build(deps): bump flake8 from 7.0.0 to 7.1.0
2024-06-26 17:07:49 +02:00
Moritz
28fc671ad5 Merge pull request #2166 from mandiant/dependabot/pip/requests-2.32.3
build(deps): bump requests from 2.31.0 to 2.32.3
2024-06-26 17:07:28 +02:00
Moritz
e1b750f1e9 Merge pull request #2167 from mandiant/dependabot/pip/psutil-6.0.0
build(deps): bump psutil from 5.9.2 to 6.0.0
2024-06-26 17:07:05 +02:00
dependabot[bot]
1ec680856d build(deps): bump requests from 2.31.0 to 2.32.3
Bumps [requests](https://github.com/psf/requests) from 2.31.0 to 2.32.3.
- [Release notes](https://github.com/psf/requests/releases)
- [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md)
- [Commits](https://github.com/psf/requests/compare/v2.31.0...v2.32.3)

---
updated-dependencies:
- dependency-name: requests
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-26 14:24:17 +00:00
dependabot[bot]
d79ea074f2 build(deps): bump flake8 from 7.0.0 to 7.1.0
Bumps [flake8](https://github.com/pycqa/flake8) from 7.0.0 to 7.1.0.
- [Commits](https://github.com/pycqa/flake8/compare/7.0.0...7.1.0)

---
updated-dependencies:
- dependency-name: flake8
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-26 14:23:05 +00:00
dependabot[bot]
e68bcddfe0 build(deps): bump psutil from 5.9.2 to 6.0.0
Bumps [psutil](https://github.com/giampaolo/psutil) from 5.9.2 to 6.0.0.
- [Changelog](https://github.com/giampaolo/psutil/blob/master/HISTORY.rst)
- [Commits](https://github.com/giampaolo/psutil/compare/release-5.9.2...release-6.0.0)

---
updated-dependencies:
- dependency-name: psutil
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-26 14:05:14 +00:00
Moritz
4929d5936e Update macos 12 (#2174)
* update CI to use macos-12 instead of macos-11
2024-06-26 16:03:45 +02:00
Mike Hunhoff
9be35f9a8d vmray: remove unneeded unpacking 2024-06-20 15:19:55 -06:00
Mike Hunhoff
ec6c9c93bd vmray: remove unused fields from summary_v2 pydantic models 2024-06-20 14:42:42 -06:00
Mike Hunhoff
9df611ff13 vmray: add comments 2024-06-20 14:41:50 -06:00
Mike Hunhoff
29fa3153b1 vmray: fix deptry lints 2024-06-20 14:17:42 -06:00
Mike Hunhoff
4b08e62750 vmray: fix flake8 lints 2024-06-20 14:12:34 -06:00
Mike Hunhoff
544899a04e vmray: add os v. monitor id comment 2024-06-20 14:06:04 -06:00
Mike Hunhoff
9ef705a9ac vmray: remove old comments 2024-06-20 14:04:31 -06:00
Mike Hunhoff
19502efff3 vmray: connect process, thread, and call 2024-06-20 13:05:32 -06:00
Mike Hunhoff
ec21f3b3fc vmray: use xmltodict instead of pydantic_xml to improve performance 2024-06-20 10:08:27 -06:00
Mike Hunhoff
5be68d0751 vmray: remove debug code and update call features entry point 2024-06-20 08:20:00 -06:00
Moritz
8757dad054 Merge pull request #2155 from r-sm2024/vmray_extractor
Add VMRayAnalysis model and call parser
2024-06-19 17:30:46 +02:00
mr-tz
0c9d3d09af fix ruff 2024-06-19 15:13:11 +00:00
mr-tz
740c739356 remove file 2024-06-19 15:09:34 +00:00
mr-tz
d256cc867f update model and re-add summary_v2.json models 2024-06-19 14:57:05 +00:00
mr-tz
fbdfea1edc add testing code 2024-06-19 14:56:12 +00:00
mr-tz
453a640de9 formatting 2024-06-19 14:55:43 +00:00
mr-tz
d10b396300 add pydantic-xml dependency 2024-06-19 14:50:46 +00:00
mr-tz
a544aed552 add vmray-extractor branch for tests 2024-06-19 14:49:12 +00:00
Moritz
a1a171221f Merge branch 'vmray-extractor' into vmray_extractor 2024-06-19 10:56:40 +02:00
Mike Hunhoff
21887d1ec6 vmray: merge upstream 2024-06-18 15:43:19 -06:00
r-sm2024
789332ec88 Merge branch 'vmray-extractor' into vmray_extractor 2024-06-18 16:41:36 -05:00
Mike Hunhoff
85a85e99bf vmray: emit recorded artifacts as strings 2024-06-18 15:38:44 -06:00
r-sm2024
574d61ad8f Add VMRayanalysis model and call parser 2024-06-18 21:33:50 +00:00
r-sm2024
3cca80860d Add VMRayanalysis model and call parser 2024-06-18 21:32:40 +00:00
r-sm2024
2b70086467 Add VMRayanalysis model and call parser 2024-06-18 21:32:40 +00:00
Mike Hunhoff
d26a806647 vmray: update scripts/show-features.py to emit process name from extractor 2024-06-18 14:59:29 -06:00
Mike Hunhoff
e5fa800ffb vmray: emit empty thread features 2024-06-18 14:45:08 -06:00
r-sm2024
be274d1d65 Merge branch 'mandiant:master' into vmray_extractor 2024-06-18 15:42:52 -05:00
Mike Hunhoff
b3ebf80d9b vmray: emit process name 2024-06-18 14:41:47 -06:00
Mike Hunhoff
8f32b7fc65 vmray: emit process handles 2024-06-18 14:32:11 -06:00
Mike Hunhoff
f3d69529b0 vmray: invoke VMRay feature extractor from capa.main 2024-06-18 13:27:40 -06:00
ygasparis
1975b6455c extract import / export symbols from stripped elf binaries (#2142) 2024-06-18 12:38:02 -06:00
Mike Hunhoff
51656fe825 vmray: merge upstream 2024-06-18 10:53:32 -06:00
Capa Bot
1360e08389 Sync capa-testfiles submodule 2024-06-18 11:00:26 +00:00
dependabot[bot]
40061b3c42 build(deps): bump viv-utils from 0.7.9 to 0.7.11 (#2150) 2024-06-18 06:36:10 +02:00
dependabot[bot]
45fca7adea build(deps): bump python-flirt from 0.8.6 to 0.8.10 (#2151) 2024-06-18 06:35:50 +02:00
Mike Hunhoff
654804878f vmray: clean up global_.py debug output 2024-06-14 09:34:59 -06:00
Mike Hunhoff
8b913e0544 vmray: extract global features for PE files 2024-06-14 09:32:02 -06:00
Moritz
482686ab81 Merge pull request #2147 from mandiant/release/v710
bump to v7.1.0
2024-06-14 12:56:46 +02:00
mr-tz
67f8c4d28c bump to v7.1.0 2024-06-14 09:06:04 +00:00
Capa Bot
3f151a342b Sync capa rules submodule 2024-06-14 09:02:02 +00:00
Mike Hunhoff
00cb7924e1 vmray: clean up pydantic models and add sample hash extraction 2024-06-13 17:02:50 -06:00
Mike Hunhoff
7e079d4d35 vmray: restrict analysis to PE files 2024-06-13 16:52:25 -06:00
Mike Hunhoff
346a0693ad vmray: clean up VMRayAnalysis 2024-06-13 16:48:12 -06:00
Mike Hunhoff
8d3f032434 vmray: clean up pydantic models and implement base address extraction 2024-06-13 16:43:23 -06:00
Mike Hunhoff
7d0ac71353 vmray: cleanup pydantic models and implement file section extraction 2024-06-13 16:31:12 -06:00
Mike Hunhoff
970b184651 vmray: add stubs for file imports 2024-06-13 14:20:11 -06:00
Mike Hunhoff
ca02b4ac7c vmray: expand extractor to emit file export features 2024-06-13 14:12:41 -06:00
Mike Hunhoff
a797405648 vmray: add example models for summary_v2.json 2024-06-13 12:54:59 -06:00
mr-tz
a9dafe283c example using pydantic-xml to parse flog.xml 2024-06-13 16:37:45 +00:00
dependabot[bot]
e87e8484b6 build(deps): bump ruff from 0.4.7 to 0.4.8 (#2139)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.4.7 to 0.4.8.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.4.7...v0.4.8)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Willi Ballenthin <wballenthin@google.com>
2024-06-13 13:24:33 +02:00
Willi Ballenthin
8726de0d65 ELF: Detect OS from Go binaries (#1987)
* elf: read segment memory size

* elf: add routine to read mapped memory

* elf: better detect OS for binaries compiled by Go

* elf: guess OS from Go source filenames

* changelog

* elf: mypy

* merge

* elf: add OS detection based on vDSO strings

* elf: document VTGrep searches

* elf: describe further technique to identify Go binaries

* elf: search for `.go.buildinfo` section via @yelhamer

* black

* elf: detect Alpine Linux ident

* elf: log interest symtab entries

* tests: add test for OS detection by Go buildinfo

* loader: handle missing viv modules

* pre-commit: run deptry before tests (which are slow)

* loader: describe removing viv symbolic switch solver

* pyproject: add PyGithub for deptry

* black
2024-06-13 13:23:47 +02:00
Moritz
7d1512a3de Merge pull request #2146 from mandiant/fix/2145
fix black and mypy
2024-06-13 11:49:18 +02:00
Capa Bot
73d76d7aba Sync capa-testfiles submodule 2024-06-13 09:30:44 +00:00
mr-tz
1febb224d1 add scripts dependency group 2024-06-13 07:50:58 +00:00
Moritz
e3ea60d354 Apply suggestions from code review
Co-authored-by: Willi Ballenthin <wballenthin@google.com>
2024-06-13 09:36:12 +02:00
mr-tz
93cd1dcedd add scripts to install step 2024-06-12 15:24:10 +00:00
mr-tz
7b0270980d add capa2sarif dependencies 2024-06-12 15:19:24 +00:00
mr-tz
cce7774705 add scripts section 2024-06-12 15:17:31 +00:00
mr-tz
9ec9a6f439 fix mypy issues 2024-06-12 09:32:03 +00:00
mr-tz
97a3fba2c9 fix black 2024-06-12 09:24:16 +00:00
Capa Bot
893352756f Sync capa rules submodule 2024-06-11 18:11:24 +00:00
malwarefrank
0cc06aa83d dnfile 0.15.0 changed API (#2037)
* dnfile 0.15.0 changed API

* deduplicate str() calls and isort fixes

* revert accidental change to imports ordering

* add table variable annotation

---------

Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
Co-authored-by: mr-tz <moritz.raabe@mandiant.com>
2024-06-11 11:46:09 -06:00
r-sm2024
bdc94c13ac Merge branch 'master' into vmray_extractor 2024-06-11 08:35:48 -05:00
dependabot[bot]
1888d0e7e3 build(deps): bump setuptools from 69.5.1 to 70.0.0 (#2135)
Bumps [setuptools](https://github.com/pypa/setuptools) from 69.5.1 to 70.0.0.
- [Release notes](https://github.com/pypa/setuptools/releases)
- [Changelog](https://github.com/pypa/setuptools/blob/main/NEWS.rst)
- [Commits](https://github.com/pypa/setuptools/compare/v69.5.1...v70.0.0)

---
updated-dependencies:
- dependency-name: setuptools
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-11 15:03:56 +02:00
ReWithMe
52e24e560b FEAT(capa2sarif) Add SARIF conversion script from json output (#2093)
* feat(capa2sarif): add new sarif conversion script converting json output to sarif schema, update dependencies, and update changelog

* fix(capa2sarif): removing copy and paste transcription errors

* fix(capa2sarif): remove dependencies from pyproject toml to guarded import statements

* chore(capa2sarif): adding node in readme specifying dependency and applied auto formatter for styling

* style(capa2sarif): applied import sorting and fixed typo in invocations function

* test(capa2sarif): adding simple test for capa to sarif conversion script using existing result document

* style(capa2sarif): fixing typo in version string in usage

* style(capa2sarif): isort failing due to reordering of typehint imports

* style(capa2sarif): fixing import order as isort on local machine was not updating code

---------

Co-authored-by: ReversingWithMe <ryanv@rewith.me>
Co-authored-by: Willi Ballenthin <wballenthin@google.com>
2024-06-11 15:01:26 +02:00
dependabot[bot]
c97d2d7244 build(deps): bump pyinstaller from 6.7.0 to 6.8.0 (#2138)
Bumps [pyinstaller](https://github.com/pyinstaller/pyinstaller) from 6.7.0 to 6.8.0.
- [Release notes](https://github.com/pyinstaller/pyinstaller/releases)
- [Changelog](https://github.com/pyinstaller/pyinstaller/blob/develop/doc/CHANGES.rst)
- [Commits](https://github.com/pyinstaller/pyinstaller/compare/v6.7.0...v6.8.0)

---
updated-dependencies:
- dependency-name: pyinstaller
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-11 14:36:58 +02:00
Willi Ballenthin
833ec47170 relax pyproject dependency versions and introduce requirements.txt (#2132)
* relax pyproject dependency versions and introduce requirements.txt

closes #2053
closes #2079

* pyproject: document dev/build profile dependency policies

* changelog

* doc: installation: describe requirements.txt usage

* pyproject: don't use dnfile 0.15 yet

---------

Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2024-06-11 14:29:34 +02:00
Willi Ballenthin
07ae30875c features: add aarch64 arch (#2144)
* features: add aarch64 arch
2024-06-11 09:36:04 +02:00
r-sm2024
3141e940de Add vmray text to JSON parser. 2024-06-10 21:13:16 +00:00
Willi Ballenthin
76a4a5899f test_scripts: avoid unsupported logic combinations 2024-06-07 05:54:49 +02:00
Willi Ballenthin
4d81b7ab98 rules: add references to existing issues 2024-06-07 05:54:49 +02:00
Willi Ballenthin
b068890fa6 rules: match: optimize rule matching by better indexing rule by features
Implement the "tighten rule pre-selection" algorithm described here:
https://github.com/mandiant/capa/issues/2063#issuecomment-2100498720

In summary:

> Rather than indexing all features from all rules,
> we should pick and index the minimal set (ideally, one) of
> features from each rule that must be present for the rule to match.
> When we have multiple candidates, pick the feature that is
> probably most uncommon and therefore "selective".

This seems to work pretty well. Total evaluations when running against
mimikatz drop from 19M to 1.1M (wow!) and capa seems to match around
3x more functions per second (wow wow).

When doing large scale runs, capa is about 25% faster when using the
vivisect backend (analysis heavy) or 3x faster when using the
upcoming BinExport2 backend (minimal analysis).
2024-06-07 05:54:49 +02:00
dependabot[bot]
d10d2820b2 build(deps): bump types-requests from 2.32.0.20240523 to 2.32.0.20240602
Bumps [types-requests](https://github.com/python/typeshed) from 2.32.0.20240523 to 2.32.0.20240602.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-requests
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-06 10:43:08 +02:00
Capa Bot
5239e40beb Sync capa-testfiles submodule 2024-06-05 12:15:41 +00:00
Capa Bot
bce8f7b5e5 Sync capa rules submodule 2024-06-05 09:40:58 +00:00
Capa Bot
0cf9365816 Sync capa-testfiles submodule 2024-06-05 08:49:12 +00:00
Fariss
30d23c4d97 render maec/* fields (#2087)
* Render maec/* fields

* add test for render_maec

---------

Co-authored-by: Soufiane Fariss <soufiane.fariss@um5s.net.ma>
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2024-06-05 10:31:13 +02:00
Capa Bot
b3ed42f5f9 Sync capa-testfiles submodule 2024-06-04 21:25:58 +00:00
Fariss
508a09ef25 include rule caching in PyInstaller build process (#2097)
* include rule caching in PyInstaller build process

The following commit introduces a new function that caches the capa
rule set, so that users don't have to manually run ./scripts/cache-
ruleset.py, before running pyinstaller.

* ci: omit Cache rule set step from build.yml workflow

* refactor: move cache generation to cache.py

* mkdir cache directory when it does not exist

---------

Co-authored-by: Soufiane Fariss <soufiane.fariss@um5s.net.ma>
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2024-06-04 18:47:41 +02:00
Capa Bot
e517d7dd77 Sync capa rules submodule 2024-06-04 10:35:46 +00:00
Moritz
142b84f9c5 Merge pull request #2118 from mandiant/dependabot/pip/deptry-0.16.1
build(deps): bump deptry from 0.14 to 0.16.1
2024-06-04 12:33:51 +02:00
dependabot[bot]
72607c6ae5 build(deps): bump ruff from 0.4.5 to 0.4.7
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.4.5 to 0.4.7.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.4.5...v0.4.7)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-03 17:56:43 +02:00
dependabot[bot]
2fd01835dc build(deps): bump rich from 13.4.2 to 13.7.1
Bumps [rich](https://github.com/Textualize/rich) from 13.4.2 to 13.7.1.
- [Release notes](https://github.com/Textualize/rich/releases)
- [Changelog](https://github.com/Textualize/rich/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Textualize/rich/compare/v13.4.2...v13.7.1)

---
updated-dependencies:
- dependency-name: rich
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-03 17:55:55 +02:00
dependabot[bot]
80600f59c7 build(deps): bump deptry from 0.14 to 0.16.1
Bumps [deptry](https://github.com/fpgmaas/deptry) from 0.14 to 0.16.1.
- [Release notes](https://github.com/fpgmaas/deptry/releases)
- [Changelog](https://github.com/fpgmaas/deptry/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fpgmaas/deptry/compare/0.14.0...0.16.1)

---
updated-dependencies:
- dependency-name: deptry
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-03 14:07:40 +00:00
Capa Bot
1ec1185850 Sync capa-testfiles submodule 2024-06-02 14:32:18 +00:00
Moritz
22e12928a6 Merge pull request #2114 from RainRat/master
fix typos
2024-06-02 16:23:29 +02:00
RainRat
8ad74ddbb6 fix typos 2024-06-01 11:48:19 -07:00
Capa Bot
2c1d5592ca Sync capa rules submodule 2024-06-01 10:23:18 +00:00
Capa Bot
267f5e99b7 Sync capa-testfiles submodule 2024-06-01 10:19:40 +00:00
Capa Bot
6b77c50ae8 Sync capa rules submodule 2024-05-31 20:25:51 +00:00
Capa Bot
8a0a24f269 Sync capa rules submodule 2024-05-31 17:24:45 +00:00
Capa Bot
4f2494dc59 Sync capa-testfiles submodule 2024-05-31 09:35:22 +00:00
Fariss
2e5da3e2bd Add deptry support (#2085)
* Add deptry support

This commit resolves #1497.

Note: known_first_party refers to modules that are supposed to be
local, i.e. idaapi, ghidra, java, binaryninja, ... etc.

* adjust running stages for deptry hook

* adjust deptry exclusions, and humanize dependency

---------

Co-authored-by: Soufiane Fariss <soufiane.fariss@um5s.net.ma>
2024-05-31 09:43:10 +02:00
Moritz
0ac21f036c update to Ubuntu 22.04 for Binary Ninja tests 2024-05-29 14:21:02 +02:00
Moritz
4ecf3a1793 Merge pull request #2090 from mandiant/dependabot/pip/protobuf-5.27.0
build(deps): bump protobuf from 5.26.1 to 5.27.0
2024-05-29 10:21:38 +02:00
Moritz
b14db68819 Merge pull request #2091 from mandiant/dependabot/pip/types-requests-2.32.0.20240523
build(deps): bump types-requests from 2.31.0.20240406 to 2.32.0.20240523
2024-05-29 10:21:25 +02:00
Moritz
54106d60ae Merge pull request #2092 from mandiant/dependabot/pip/pyinstaller-6.7.0
build(deps): bump pyinstaller from 6.6.0 to 6.7.0
2024-05-29 10:21:14 +02:00
Capa Bot
0622f45208 Sync capa-testfiles submodule 2024-05-28 13:44:27 +00:00
Moritz
adb9de8d4b Merge pull request #2089 from mandiant/dependabot/pip/ruff-0.4.5
build(deps): bump ruff from 0.4.4 to 0.4.5
2024-05-28 13:18:33 +02:00
dependabot[bot]
48dd64beba build(deps): bump protobuf from 5.26.1 to 5.27.0
Bumps [protobuf](https://github.com/protocolbuffers/protobuf) from 5.26.1 to 5.27.0.
- [Release notes](https://github.com/protocolbuffers/protobuf/releases)
- [Changelog](https://github.com/protocolbuffers/protobuf/blob/main/protobuf_release.bzl)
- [Commits](https://github.com/protocolbuffers/protobuf/compare/v5.26.1...v5.27.0)

---
updated-dependencies:
- dependency-name: protobuf
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-28 07:59:24 +00:00
Fariss
abaabae164 Replace halo spinner with rich (#2086)
* Replace halo spinner with rich

* remove Halo dependency

* Omit halo from mypy.ini

---------

Co-authored-by: Soufiane Fariss <soufiane.fariss@um5s.net.ma>
2024-05-28 09:58:32 +02:00
dependabot[bot]
8316a74ca2 build(deps): bump pyinstaller from 6.6.0 to 6.7.0
Bumps [pyinstaller](https://github.com/pyinstaller/pyinstaller) from 6.6.0 to 6.7.0.
- [Release notes](https://github.com/pyinstaller/pyinstaller/releases)
- [Changelog](https://github.com/pyinstaller/pyinstaller/blob/develop/doc/CHANGES.rst)
- [Commits](https://github.com/pyinstaller/pyinstaller/compare/v6.6.0...v6.7.0)

---
updated-dependencies:
- dependency-name: pyinstaller
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-27 14:33:10 +00:00
dependabot[bot]
1dd2af7048 build(deps): bump types-requests from 2.31.0.20240406 to 2.32.0.20240523
Bumps [types-requests](https://github.com/python/typeshed) from 2.31.0.20240406 to 2.32.0.20240523.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-requests
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-27 14:32:57 +00:00
dependabot[bot]
bbc4e5cd97 build(deps): bump ruff from 0.4.4 to 0.4.5
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.4.4 to 0.4.5.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.4.4...v0.4.5)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-27 14:32:37 +00:00
Capa Bot
7da3ef89ca Sync capa rules submodule 2024-05-23 11:37:39 +00:00
Moritz
44e319a604 Merge pull request #2081 from mandiant/dependabot/pip/mypy-protobuf-3.6.0
build(deps): bump mypy-protobuf from 3.5.0 to 3.6.0
2024-05-22 14:09:26 +02:00
Moritz
21c346d0c2 Merge pull request #2082 from mandiant/dependabot/pip/types-requests-2.31.0.20240406
build(deps): bump types-requests from 2.31.0.20240311 to 2.31.0.20240406
2024-05-22 14:09:17 +02:00
Capa Bot
f9953d1e99 Sync capa rules submodule 2024-05-21 07:58:30 +00:00
dependabot[bot]
9bce98b0ae build(deps): bump types-requests from 2.31.0.20240311 to 2.31.0.20240406
Bumps [types-requests](https://github.com/python/typeshed) from 2.31.0.20240311 to 2.31.0.20240406.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-requests
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-20 14:14:50 +00:00
dependabot[bot]
7f39a5b1d6 build(deps): bump mypy-protobuf from 3.5.0 to 3.6.0
Bumps [mypy-protobuf](https://github.com/nipunn1313/mypy-protobuf) from 3.5.0 to 3.6.0.
- [Changelog](https://github.com/nipunn1313/mypy-protobuf/blob/main/CHANGELOG.md)
- [Commits](https://github.com/nipunn1313/mypy-protobuf/compare/v3.5.0...v3.6.0)

---
updated-dependencies:
- dependency-name: mypy-protobuf
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-20 14:14:47 +00:00
Moritz
e9cc193dd4 Merge pull request #2077 from mandiant/dependabot/pip/tqdm-4.66.4
build(deps): bump tqdm from 4.66.3 to 4.66.4
2024-05-16 14:15:15 +02:00
Moritz
5482021c75 Merge pull request #2076 from mandiant/dependabot/pip/flake8-bugbear-24.4.26
build(deps): bump flake8-bugbear from 24.2.6 to 24.4.26
2024-05-16 14:14:58 +02:00
Moritz
5507991575 Merge pull request #2078 from mandiant/dependabot/pip/ruff-0.4.4
build(deps): bump ruff from 0.3.5 to 0.4.4
2024-05-16 14:13:01 +02:00
dependabot[bot]
65114ec2d7 build(deps): bump ruff from 0.3.5 to 0.4.4
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.3.5 to 0.4.4.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.3.5...v0.4.4)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-13 14:54:11 +00:00
dependabot[bot]
e4ae052f48 build(deps): bump tqdm from 4.66.3 to 4.66.4
Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.66.3 to 4.66.4.
- [Release notes](https://github.com/tqdm/tqdm/releases)
- [Commits](https://github.com/tqdm/tqdm/compare/v4.66.3...v4.66.4)

---
updated-dependencies:
- dependency-name: tqdm
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-13 14:53:54 +00:00
dependabot[bot]
3ae8183a4a build(deps): bump flake8-bugbear from 24.2.6 to 24.4.26
Bumps [flake8-bugbear](https://github.com/PyCQA/flake8-bugbear) from 24.2.6 to 24.4.26.
- [Release notes](https://github.com/PyCQA/flake8-bugbear/releases)
- [Commits](https://github.com/PyCQA/flake8-bugbear/compare/24.2.6...24.4.26)

---
updated-dependencies:
- dependency-name: flake8-bugbear
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-13 14:53:49 +00:00
Willi Ballenthin
b59df659c9 pep8 2024-05-08 16:20:10 +02:00
Willi Ballenthin
519cfb842e profile-time: more result reporting, and learn to specify other backends 2024-05-08 16:20:10 +02:00
Capa Bot
ee98548bf9 Sync capa-testfiles submodule 2024-05-07 22:20:48 +00:00
mr-tz
8298347c19 support more report formats 2024-05-07 15:24:21 +02:00
dependabot[bot]
54d749e845 build(deps): bump types-protobuf from 4.23.0.3 to 5.26.0.20240422
Bumps [types-protobuf](https://github.com/python/typeshed) from 4.23.0.3 to 5.26.0.20240422.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-protobuf
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-07 15:21:57 +02:00
dependabot[bot]
25b9c88198 build(deps): bump black from 24.4.0 to 24.4.2
Bumps [black](https://github.com/psf/black) from 24.4.0 to 24.4.2.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/24.4.0...24.4.2)

---
updated-dependencies:
- dependency-name: black
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-07 15:21:45 +02:00
dependabot[bot]
11ae44541b build(deps): bump mypy from 1.9.0 to 1.10.0
Bumps [mypy](https://github.com/python/mypy) from 1.9.0 to 1.10.0.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/1.9.0...v1.10.0)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-07 15:21:29 +02:00
Willi Ballenthin
f26a109b4d pep8 2024-05-07 15:21:14 +02:00
Willi Ballenthin
d26897afca capabilities: don't show progress bar when stderr is redirected to a file 2024-05-07 15:21:14 +02:00
Willi Ballenthin
6869ef6520 engine, common: use FeatureSet type annotation for evaluate signature
It was used in some places already, but now used everywhere consistently.
This should make it easier to refactor the FeatureSet type, if necessary,
because its easier to see all the places its used.
2024-05-07 15:20:50 +02:00
Willi Ballenthin
4fbd2ba2b8 capabilities: fix duplicate name 2024-05-07 15:20:16 +02:00
Willi Ballenthin
283ce41a5e capabilities: only log "real" matched rules, not derived count 2024-05-07 15:20:16 +02:00
dependabot[bot]
4b1a5003df build(deps-dev): bump protobuf from 4.23.4 to 5.26.1
Bumps [protobuf](https://github.com/protocolbuffers/protobuf) from 4.23.4 to 5.26.1.
- [Release notes](https://github.com/protocolbuffers/protobuf/releases)
- [Changelog](https://github.com/protocolbuffers/protobuf/blob/main/protobuf_release.bzl)
- [Commits](https://github.com/protocolbuffers/protobuf/compare/v4.23.4...v5.26.1)

---
updated-dependencies:
- dependency-name: protobuf
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-06 12:49:25 +02:00
dependabot[bot]
1cd0f44115 build(deps): bump tqdm from 4.66.2 to 4.66.3
Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.66.2 to 4.66.3.
- [Release notes](https://github.com/tqdm/tqdm/releases)
- [Commits](https://github.com/tqdm/tqdm/compare/v4.66.2...v4.66.3)

---
updated-dependencies:
- dependency-name: tqdm
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-06 12:44:02 +02:00
Moritz
824e852184 Merge pull request #2059 from mandiant/mr-tz-patch-1
Update pydantic to 2.7.1
2024-05-03 10:45:08 +02:00
Capa Bot
4be0c40fe6 Sync capa rules submodule 2024-05-03 08:42:32 +00:00
RainRat
4f4adc04c8 fix typos 2024-05-02 21:24:59 +02:00
Moritz
60d400cf08 Update pydantic to 2.7.1 2024-04-30 12:07:02 +02:00
Moritz
2f4d8e1d90 Merge pull request #2057 from mandiant/dependabot/pip/ruamel-yaml-0.18.6
build(deps): bump ruamel-yaml from 0.18.5 to 0.18.6
2024-04-30 12:01:44 +02:00
Moritz
fdfa838a15 Merge pull request #2055 from mandiant/dependabot/pip/pyelftools-0.31
build(deps): bump pyelftools from 0.30 to 0.31
2024-04-30 12:01:33 +02:00
Moritz
baef70d588 Merge pull request #2054 from mandiant/dependabot/pip/pyinstaller-6.6.0
build(deps): bump pyinstaller from 6.4.0 to 6.6.0
2024-04-30 12:01:19 +02:00
Moritz
e24773436e Merge pull request #2058 from RainRat/master
fix typos
2024-04-30 11:57:46 +02:00
RainRat
a4a4016463 fix typos 2024-04-29 23:31:15 -07:00
dependabot[bot]
30535cb623 build(deps): bump ruamel-yaml from 0.18.5 to 0.18.6
Bumps [ruamel-yaml]() from 0.18.5 to 0.18.6.

---
updated-dependencies:
- dependency-name: ruamel-yaml
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-29 14:43:49 +00:00
dependabot[bot]
2355603340 build(deps): bump pyelftools from 0.30 to 0.31
Bumps [pyelftools](https://github.com/eliben/pyelftools) from 0.30 to 0.31.
- [Changelog](https://github.com/eliben/pyelftools/blob/main/CHANGES)
- [Commits](https://github.com/eliben/pyelftools/compare/v0.30...v0.31)

---
updated-dependencies:
- dependency-name: pyelftools
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-29 14:43:33 +00:00
dependabot[bot]
9a23e6837d build(deps): bump pyinstaller from 6.4.0 to 6.6.0
Bumps [pyinstaller](https://github.com/pyinstaller/pyinstaller) from 6.4.0 to 6.6.0.
- [Release notes](https://github.com/pyinstaller/pyinstaller/releases)
- [Changelog](https://github.com/pyinstaller/pyinstaller/blob/develop/doc/CHANGES.rst)
- [Commits](https://github.com/pyinstaller/pyinstaller/compare/v6.4.0...v6.6.0)

---
updated-dependencies:
- dependency-name: pyinstaller
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-29 14:43:29 +00:00
Capa Bot
0488c86bc7 Sync capa rules submodule 2024-04-29 09:49:01 +00:00
Moritz
b4092980e3 Merge pull request #2052 from mandiant/dependabot/pip/build-1.2.1
build(deps): bump build from 1.0.3 to 1.2.1
2024-04-26 13:27:31 +02:00
Moritz
18bdf23f03 Merge pull request #2051 from mandiant/dependabot/pip/mypy-1.9.0
build(deps): bump mypy from 1.8.0 to 1.9.0
2024-04-26 13:27:21 +02:00
Moritz
ac6e9f8aae Merge pull request #2040 from mandiant/dependabot/pip/pytest-cov-5.0.0
build(deps-dev): bump pytest-cov from 4.1.0 to 5.0.0
2024-04-26 13:27:09 +02:00
Capa Bot
abb6d01c1d Sync capa rules submodule 2024-04-24 11:38:35 +00:00
Capa Bot
984c1b2d39 Sync capa-testfiles submodule 2024-04-23 16:47:43 +00:00
Capa Bot
e3dcbbb386 Sync capa rules submodule 2024-04-23 15:04:54 +00:00
Capa Bot
a8f382ebe8 Sync capa rules submodule 2024-04-23 12:21:09 +00:00
dependabot[bot]
4fb10780ec build(deps): bump build from 1.0.3 to 1.2.1
Bumps [build](https://github.com/pypa/build) from 1.0.3 to 1.2.1.
- [Release notes](https://github.com/pypa/build/releases)
- [Changelog](https://github.com/pypa/build/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pypa/build/compare/1.0.3...1.2.1)

---
updated-dependencies:
- dependency-name: build
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-22 14:31:16 +00:00
dependabot[bot]
efc7540aa6 build(deps): bump mypy from 1.8.0 to 1.9.0
Bumps [mypy](https://github.com/python/mypy) from 1.8.0 to 1.9.0.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.8.0...1.9.0)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-22 14:31:12 +00:00
Capa Bot
f1c4ff8e17 Sync capa rules submodule 2024-04-22 08:33:05 +00:00
Capa Bot
f44b4ebebd Sync capa-testfiles submodule 2024-04-19 12:32:37 +00:00
Moritz
19000409df Merge pull request #2048 from mandiant/dependabot/pip/setuptools-69.5.1
build(deps): bump setuptools from 69.0.3 to 69.5.1
2024-04-16 10:59:06 +02:00
Moritz
42849573b3 Merge pull request #2047 from mandiant/dependabot/pip/black-24.4.0
build(deps): bump black from 24.3.0 to 24.4.0
2024-04-16 10:58:55 +02:00
dependabot[bot]
c02440f4b7 build(deps): bump setuptools from 69.0.3 to 69.5.1
Bumps [setuptools](https://github.com/pypa/setuptools) from 69.0.3 to 69.5.1.
- [Release notes](https://github.com/pypa/setuptools/releases)
- [Changelog](https://github.com/pypa/setuptools/blob/main/NEWS.rst)
- [Commits](https://github.com/pypa/setuptools/compare/v69.0.3...v69.5.1)

---
updated-dependencies:
- dependency-name: setuptools
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-15 14:54:55 +00:00
dependabot[bot]
676f98acc8 build(deps): bump black from 24.3.0 to 24.4.0
Bumps [black](https://github.com/psf/black) from 24.3.0 to 24.4.0.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/24.3.0...24.4.0)

---
updated-dependencies:
- dependency-name: black
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-15 14:53:33 +00:00
Capa Bot
e3a9c75316 Sync capa-testfiles submodule 2024-04-09 10:47:12 +00:00
Capa Bot
2a54689cc6 Sync capa-testfiles submodule 2024-04-09 08:33:18 +00:00
dependabot[bot]
cd11787bd8 build(deps-dev): bump ruff from 0.2.1 to 0.3.5
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.2.1 to 0.3.5.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.2.1...v0.3.5)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-08 16:29:12 +02:00
Moritz
9171dc2dad Merge pull request #2044 from acelynnzhang/dynamic-signature-fix
Restrict signature debug logging to vivisect backend
2024-04-03 07:20:29 +02:00
Acelynn Zhang
c695b37b0e Restrict signature debug logging to vivisect backend
Closes #1875
2024-04-02 09:49:04 -05:00
dependabot[bot]
e1d0ba22c7 build(deps-dev): bump pytest-cov from 4.1.0 to 5.0.0
Bumps [pytest-cov](https://github.com/pytest-dev/pytest-cov) from 4.1.0 to 5.0.0.
- [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-cov/compare/v4.1.0...v5.0.0)

---
updated-dependencies:
- dependency-name: pytest-cov
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-25 14:03:56 +00:00
Capa Bot
7debc54dbd Sync capa-testfiles submodule 2024-03-24 08:31:37 +00:00
Moritz
7b50065fea Merge pull request #2026 from mandiant/dependabot/pip/types-requests-2.31.0.20240311
build(deps-dev): bump types-requests from 2.31.0.20240125 to 2.31.0.20240311
2024-03-22 11:13:18 +01:00
Moritz
37306af37a Merge pull request #2030 from mandiant/dependabot/pip/black-24.3.0
build(deps-dev): bump black from 24.1.1 to 24.3.0
2024-03-22 11:12:58 +01:00
Moritz
c03405c29f Merge pull request #1996 from mandiant/dependabot/pip/flake8-todos-0.3.1
build(deps-dev): bump flake8-todos from 0.3.0 to 0.3.1
2024-03-22 11:06:05 +01:00
Moritz
8fe8981570 Update scorecard.yml (#2033)
* Update scorecard.yml
2024-03-22 08:57:29 +01:00
Moritz
463f2f1d62 Merge pull request #2017 from xusheng6/test_binja_4_0
Test binja 4 0
2024-03-21 13:19:26 +01:00
Moritz
9a5f4562b8 Merge branch 'master' into test_binja_4_0 2024-03-21 12:13:41 +01:00
Abdul Samad Siddiqui
7bc298de1a Emit "dotnet" as format to ResultDocument when processing .NET files (#2024)
* Refactor format in `capa/features/extractors/dotnetfile.py`

Signed-off-by: samadpls <abdulsamadsid1@gmail.com>

* updated chanalog.md with the changes

Signed-off-by: samadpls <abdulsamadsid1@gmail.com>

* Refractor CHANGELOG.md

Co-authored-by: Mike Hunhoff <mike.hunhoff@gmail.com>

---------

Signed-off-by: samadpls <abdulsamadsid1@gmail.com>
Co-authored-by: Mike Hunhoff <mike.hunhoff@gmail.com>
2024-03-20 11:07:05 -06:00
Moritz
cbadab8521 Add faq (#2032)
* Create faq.md

---------

Co-authored-by: Vasco Schiavo <115561717+VascoSch92@users.noreply.github.com>
2024-03-20 14:59:02 +01:00
dependabot[bot]
0eaf055a46 build(deps-dev): bump black from 24.1.1 to 24.3.0
Bumps [black](https://github.com/psf/black) from 24.1.1 to 24.3.0.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/24.1.1...24.3.0)

---
updated-dependencies:
- dependency-name: black
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-18 14:38:51 +00:00
N0stalgikow
0eb4291b25 Updating copyright across all files based on when it was first introduced. (#2027)
* updating copyright, back to the date of origin of file

* updating regex to account for linter violation
2024-03-13 14:04:53 +01:00
Fariss
9d1f110d24 ida-explorer: replace deprecated IDA API find_binary with bin_search (#2011)
* ida-explorer: replace deprecated IDA API find_binary with bin_search

* Fix packages import sort order

* Modify code style: return on error in find_byte_sequence

* Declare global variables for find_byte_sequence

* Declare global variables for find_byte_sequence

* Declare global variables for find_byte_sequence

* remove IDA_BYTES_PATTERNS, because ida_bytes.parse_bin_pat_str modifies first param
2024-03-11 13:04:16 -06:00
dependabot[bot]
0f0a23946b build(deps-dev): bump types-requests
Bumps [types-requests](https://github.com/python/typeshed) from 2.31.0.20240125 to 2.31.0.20240311.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-requests
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-11 14:39:04 +00:00
Mike Hunhoff
5b2122a3c6 Update capa + Ghidra README.md (#2023) 2024-03-06 11:01:39 -07:00
Aayush Goel
49231366f1 Handles circular dependencies while getting rules and dependencies (#2014)
* Remove test for scope "unspecified"

* raise error on circular dependency

* test for circular dependency
2024-03-06 11:39:21 +01:00
Capa Bot
10a4381ad5 Sync capa-testfiles submodule 2024-03-05 15:45:40 +00:00
Moritz
7707984237 Merge branch 'master' into test_binja_4_0 2024-03-04 16:21:11 +01:00
N0stalgikow
f6b0673b0f Adding a citation file to capa (#2018)
* Adding a citation file to capa

---------

Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2024-03-04 10:56:58 +01:00
Capa Bot
1c1e5c02b0 Sync capa rules submodule 2024-03-04 09:27:58 +00:00
Willi Ballenthin
fe13f9ce76 Merge branch 'master' into test_binja_4_0 2024-03-03 07:20:56 +01:00
P.Sahithi Reddy
04e3f268f3 Update github ci workflow to reflect new ghidrathon installation (#2020)
* Update github ci workflow to reflect new ghidrathon release installation

* Update CHANGELOG
2024-03-01 16:24:02 -07:00
Xusheng
12234c3572 Update changelog 2024-02-29 17:07:37 +08:00
Xusheng
92cfc0caa7 binja: add support for forwarded export and enable the related unit tests 2024-02-29 17:07:37 +08:00
Xusheng
58e4a30156 binja: fix stack string detection and always use builtin function outlining 2024-02-29 17:07:37 +08:00
Xusheng
bf4695c6bf binja: update binja version check after 4.0 release 2024-02-29 16:54:54 +08:00
Ana María Martínez Gómez
d63c6f1f9e Merge pull request #2003 from Ana06/update-actions
[CI] Update GitHub actions to versions using Node 20
2024-02-27 13:21:38 -05:00
Capa Bot
08b3ae60d7 Sync capa rules submodule 2024-02-27 11:56:47 +00:00
Ana Maria Martinez Gomez
f5893d7bd3 [changelog] Add actions update 2024-02-27 12:49:28 +01:00
Ana Maria Martinez Gomez
3a90247e5b [CI] Update github/codeql-action/upload-sarif
The old version was using a deprecated version of Node.
2024-02-27 12:46:41 +01:00
Ana Maria Martinez Gomez
bb0dff0610 [CI] Update gradle/gradle-build-action
Replace gradle/gradle-build-action by gradle/gradle-build-action, which
supersedes it since v3. The previous version used a deprecated version
of Node.
2024-02-27 12:46:41 +01:00
Ana Maria Martinez Gomez
610a86e5e2 [CI] Update ad-m/github-push-action
The old version was using a deprecated version of Node.
2024-02-27 12:46:40 +01:00
Ana Maria Martinez Gomez
cabb9c0975 [CI] Update Ana06/get-changed-files
Update Ana06/get-changed-files to the latest version that I released
yesterday using Node 20. The old version was using a deprecated version
of Node.
2024-02-27 12:46:40 +01:00
Ana María Martínez Gómez
c28f4fc890 Merge pull request #2004 from Ana06/changelog-review
Fix CHANGELOG PR review & update Ana06/automatic-pull-request-review
2024-02-26 17:49:28 +01:00
Rohit Konakalla
9a449b6bd9 Load .json.gz files directly (#1990)
* Load .json.gz files directly

* Add helper function to load .json and replace json.load references

* add test and update change log

* add .json.gz in EXTENSIONS_DYNAMIC

Co-authored-by: Moritz <mr-tz@users.noreply.github.com>

---------

Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2024-02-25 16:01:36 +01:00
Ana Maria Martinez Gomez
65b5c46029 [changelog] Add gist badge
I didn't originally add the gist badge to the CHANGELOG in #2001, but I
am thinking now that it may be a good idea to track it.
2024-02-23 20:22:44 +01:00
Ana Maria Martinez Gomez
8857511e55 [CI] Fix CHANGELOG PR review
Sending a PR review with a message about the CHANGELOG needing to be
updated has been broken since July, where the permissions were changed.
2024-02-23 16:25:49 +01:00
Ana Maria Martinez Gomez
ffcabf1e0b [CI] Update Ana06/automatic-pull-request-review
The old version was using a deprecated version of Node.
2024-02-23 15:49:28 +01:00
Ana María Martínez Gómez
c6b43d7492 Merge pull request #2001 from Ana06/gist_badge
[CI] Use badge in gist for rules number in README
2024-02-23 14:55:44 +01:00
Moritz
8af3a19d61 Merge pull request #2000 from sjha2048/chore/updateGithubActions
update github workflows to use latest versions
2024-02-23 13:41:06 +01:00
Ana Maria Martinez Gomez
2252e69eed [CI] Use badge in gist for rules number in README
Since mandiant/capa-rules#882, the README badge in capa is not updated
anymore via commits anymore, but in a gist.
2024-02-23 08:40:05 +01:00
Sahil
5e85fc9ede update github workflows to use latest version for depricated actions (checkout, setup-python, upload-artifact, download-artifact) 2024-02-23 02:57:07 +05:30
dependabot[bot]
4e529d5c1f build(deps-dev): bump flake8-todos from 0.3.0 to 0.3.1
Bumps [flake8-todos](https://github.com/orsinium-labs/flake8-todos) from 0.3.0 to 0.3.1.
- [Release notes](https://github.com/orsinium-labs/flake8-todos/releases)
- [Commits](https://github.com/orsinium-labs/flake8-todos/compare/0.3.0...0.3.1)

---
updated-dependencies:
- dependency-name: flake8-todos
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-19 14:52:42 +00:00
Willi Ballenthin
0f9dd9095b fmt 2024-02-14 15:57:24 +01:00
Willi Ballenthin
b163f82a71 changelog 2024-02-14 15:57:24 +01:00
Willi Ballenthin
bd3cc18a25 loader: avoid eager imports of some backend-related code 2024-02-14 15:57:24 +01:00
Willi Ballenthin
4e2f175b9f rules: don't eagerly import ruamel until needed 2024-02-14 15:57:24 +01:00
Willi Ballenthin
fdd097a141 main: remove unused imports 2024-02-14 15:57:24 +01:00
Willi Ballenthin
1b4e5258f8 elf: don't require vivisect just for type annotations 2024-02-14 15:57:24 +01:00
Capa Bot
1d78900862 Sync capa rules submodule 2024-02-14 13:57:16 +00:00
Moritz
8807d6844d Merge pull request #1984 from mandiant/dependabot/pip/tqdm-4.66.2
build(deps-dev): bump tqdm from 4.66.1 to 4.66.2
2024-02-14 11:04:31 +01:00
Moritz
318a3d1610 Merge pull request #1985 from mandiant/dependabot/pip/flake8-bugbear-24.2.6
build(deps-dev): bump flake8-bugbear from 24.1.17 to 24.2.6
2024-02-14 11:04:17 +01:00
Moritz
b86b66a29c Merge pull request #1986 from mandiant/dependabot/pip/ruff-0.2.1
build(deps-dev): bump ruff from 0.1.14 to 0.2.1
2024-02-14 11:04:02 +01:00
Moritz
c263670a21 Merge pull request #1983 from mandiant/dependabot/pip/pyinstaller-6.4.0
build(deps-dev): bump pyinstaller from 6.3.0 to 6.4.0
2024-02-14 11:03:43 +01:00
Moritz
fc840d8e7d Merge pull request #1974 from mandiant/dependabot/pip/pytest-sugar-1.0.0
build(deps-dev): bump pytest-sugar from 0.9.7 to 1.0.0
2024-02-14 11:03:22 +01:00
dependabot[bot]
b751a7bba3 build(deps-dev): bump ruff from 0.1.14 to 0.2.1
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.1.14 to 0.2.1.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.1.14...v0.2.1)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-12 14:47:30 +00:00
dependabot[bot]
c8765a4116 build(deps-dev): bump flake8-bugbear from 24.1.17 to 24.2.6
Bumps [flake8-bugbear](https://github.com/PyCQA/flake8-bugbear) from 24.1.17 to 24.2.6.
- [Release notes](https://github.com/PyCQA/flake8-bugbear/releases)
- [Commits](https://github.com/PyCQA/flake8-bugbear/compare/24.1.17...24.2.6)

---
updated-dependencies:
- dependency-name: flake8-bugbear
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-12 14:47:14 +00:00
dependabot[bot]
4955a23c52 build(deps-dev): bump tqdm from 4.66.1 to 4.66.2
Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.66.1 to 4.66.2.
- [Release notes](https://github.com/tqdm/tqdm/releases)
- [Commits](https://github.com/tqdm/tqdm/compare/v4.66.1...v4.66.2)

---
updated-dependencies:
- dependency-name: tqdm
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-12 14:47:09 +00:00
dependabot[bot]
16814c376f build(deps-dev): bump pyinstaller from 6.3.0 to 6.4.0
Bumps [pyinstaller](https://github.com/pyinstaller/pyinstaller) from 6.3.0 to 6.4.0.
- [Release notes](https://github.com/pyinstaller/pyinstaller/releases)
- [Changelog](https://github.com/pyinstaller/pyinstaller/blob/develop/doc/CHANGES.rst)
- [Commits](https://github.com/pyinstaller/pyinstaller/compare/v6.3.0...v6.4.0)

---
updated-dependencies:
- dependency-name: pyinstaller
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-12 14:47:00 +00:00
Mike Hunhoff
05fb1a5c00 Update Ghidra README (#1979) 2024-02-09 08:58:47 -07:00
dependabot[bot]
df8056f415 build(deps-dev): bump pytest-sugar from 0.9.7 to 1.0.0
Bumps [pytest-sugar](https://github.com/Teemu/pytest-sugar) from 0.9.7 to 1.0.0.
- [Release notes](https://github.com/Teemu/pytest-sugar/releases)
- [Changelog](https://github.com/Teemu/pytest-sugar/blob/main/CHANGES.rst)
- [Commits](https://github.com/Teemu/pytest-sugar/compare/v0.9.7...v1.0.0)

---
updated-dependencies:
- dependency-name: pytest-sugar
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-05 14:45:41 +00:00
Capa Bot
fde1de3250 Sync capa rules submodule 2024-02-05 09:34:46 +00:00
Capa Bot
7ab8dbbd4e Sync capa-testfiles submodule 2024-02-05 09:31:34 +00:00
Moritz
2ddb6b0773 update to v7.0.1 (#1972) 2024-02-02 11:21:50 +01:00
Moritz
5fd532845c Update .gitmodules 2024-02-02 10:13:36 +01:00
Willi Ballenthin
2a59284621 freeze: remove unused import (#1969)
* freeze: remove unused import

potentially causing circular import errors

---------

Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2024-02-01 19:41:44 +01:00
Moritz
9adb669921 Merge pull request #1968 from mandiant/doc/blog-links
Update blog links
2024-02-01 18:37:21 +01:00
Moritz
034894330b Update blog links 2024-02-01 15:51:49 +01:00
Moritz
a3a8e36911 Release capa version 7.0.0 (#1958)
* bump version to 7.0.0

---------

Co-authored-by: Willi Ballenthin <wballenthin@google.com>
2024-02-01 15:21:20 +01:00
Moritz
2c93c5fc83 lint: get backend from format (#1964)
* get backend from format

* add lint.py script test

* create FakeArgs object

* adjust EOL handling in lints

---------

Co-authored-by: Willi Ballenthin <wballenthin@google.com>
2024-02-01 11:33:16 +01:00
Willi Ballenthin
9929967634 changelog 2024-01-31 14:16:23 +01:00
Willi Ballenthin
3436aab3fd proto: regenerate pyi with same protoc 2024-01-31 14:16:23 +01:00
Lin Chen
9a76558fdf Add package name for CAPA proto
Having a package name is recommended in protobuf's style guide, to avoid
naming conflicts.
2024-01-31 14:16:23 +01:00
Moritz
2e5761a414 Merge pull request #1940 from mandiant/dependabot/pip/flake8-bugbear-24.1.17
build(deps-dev): bump flake8-bugbear from 23.12.2 to 24.1.17
2024-01-31 13:49:52 +01:00
Moritz
2f2d4a1d6b Merge branch 'master' into dependabot/pip/flake8-bugbear-24.1.17 2024-01-31 11:41:05 +01:00
Jensen Coonradt
1a4f2559fa Change log update to show the removal of the scripts/vivisect-py2-vs-py3.sh file (#1952)
* remove scripts/vivisect-py2-vs-py3.sh

---------

Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2024-01-31 11:37:46 +01:00
mr-tz
66c2f07ca8 remove BaseException usage 2024-01-31 11:32:00 +01:00
dependabot[bot]
75800b9d2e build(deps-dev): bump flake8-bugbear from 23.12.2 to 24.1.17
Bumps [flake8-bugbear](https://github.com/PyCQA/flake8-bugbear) from 23.12.2 to 24.1.17.
- [Release notes](https://github.com/PyCQA/flake8-bugbear/releases)
- [Commits](https://github.com/PyCQA/flake8-bugbear/compare/23.12.2...24.1.17)

---
updated-dependencies:
- dependency-name: flake8-bugbear
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-31 11:32:00 +01:00
dependabot[bot]
bae4091661 build(deps-dev): bump types-requests (#1954)
Bumps [types-requests](https://github.com/python/typeshed) from 2.31.0.20240106 to 2.31.0.20240125.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-requests
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-31 11:19:12 +01:00
dependabot[bot]
ba044a980f build(deps-dev): bump black from 23.12.1 to 24.1.1 (#1955)
* build(deps-dev): bump black from 23.12.1 to 24.1.1

Bumps [black](https://github.com/psf/black) from 23.12.1 to 24.1.1.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/23.12.1...24.1.1)

---
updated-dependencies:
- dependency-name: black
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* black 24.1.1 formatting

* update flake config to match black 24.1.1 format

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
Co-authored-by: mr-tz <moritz.raabe@mandiant.com>
2024-01-31 11:18:54 +01:00
Mike Hunhoff
2e7642ef8a update Ghidra integration (#1959) 2024-01-31 00:03:34 -07:00
Colton Gabertan
3e4479e3bb ghidra: UI integration (#1786) 2024-01-30 22:58:35 -07:00
Moritz
437732174b Merge pull request #1957 from mandiant/fix/explorer-bugs
fix rule display and save functionality
2024-01-30 17:16:43 +01:00
mr-tz
f845382471 fix rule display and save functionality 2024-01-30 15:20:16 +01:00
Moritz
06aa3f6528 Merge pull request #1956 from mandiant/dependabot/pip/pytest-8.0.0
build(deps-dev): bump pytest from 7.4.4 to 8.0.0
2024-01-30 14:12:49 +01:00
dependabot[bot]
45ebc3e3d6 build(deps-dev): bump pytest from 7.4.4 to 8.0.0
Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.4.4 to 8.0.0.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/7.4.4...8.0.0)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-29 14:50:29 +00:00
Willi Ballenthin
c3301d3b3f refactor main to for ease of integration (#1948)
* main: split main into a bunch of "main routines"

[wip] since there are a few references to BinExport2
that are in progress elsewhre. Next commit will remove them.

* main: remove references to wip BinExport2 code

* changelog

* main: rename first position argument "input_file"

closes #1946

* main: linters

* main: move rule-related routines to capa.rules

ref #1821

* main: extract routines to capa.loader module

closes #1821

* add loader module

* loader: learn to load freeze format

* freeze: use new cli arg handling

* Update capa/loader.py

Co-authored-by: Moritz <mr-tz@users.noreply.github.com>

* main: remove duplicate documentation

* main: add doc about where some functions live

* scripts: migrate to new main wrapper helper functions

* scripts: port to main routines

* main: better handle auto-detection of backend

* scripts: migrate bulk-process to main wrappers

* scripts: migrate scripts to main wrappers

* main: rename *_from_args to *_from_cli

* changelog

* cache-ruleset: remove duplication

* main: fix tag handling

* cache-ruleset: fix cli args

* cache-ruleset: fix special rule cli handling

* scripts: fix type bytes

* main: remove old TODO message

* loader: fix references to binja extractor

---------

Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2024-01-29 13:59:05 +01:00
Willi Ballenthin
d2e1a47192 more ELF OS detection techniques (#1947)
* elf: os: deprioritize .ident strategy due to potential for FPs

* elf: os: same as parent, fix .ident FP

* elf: os: detect Android via clang compiler .ident note

* elf: os: detect Android via dependency on liblog.so

* changelog
2024-01-25 16:26:31 +01:00
Moritz
85e1495fed update to v7-beta (#1942)
* update to v7-beta
2024-01-24 14:55:54 +01:00
Moritz
35ec5511e4 Update capa explorer scopes in rule template (#1943)
* Update capa explorer scopes in rule template

* Update capa/ida/plugin/view.py
2024-01-23 09:20:16 -07:00
Capa Bot
009cf0c854 Sync capa rules submodule 2024-01-23 09:56:05 +00:00
Capa Bot
96f68620ca Sync capa rules submodule 2024-01-23 09:55:33 +00:00
Capa Bot
0676e80c20 Sync capa rules submodule 2024-01-23 09:42:16 +00:00
Capa Bot
1c89d01982 Sync capa rules submodule 2024-01-22 19:21:43 +00:00
Moritz
692aba1b1d Merge pull request #1939 from aaronatp/master
Enable tracebacks on PR build attempts
2024-01-22 20:20:25 +01:00
Moritz
7e0cd565fd Merge pull request #1941 from mandiant/dependabot/pip/ruff-0.1.14
build(deps-dev): bump ruff from 0.1.13 to 0.1.14
2024-01-22 20:04:21 +01:00
dependabot[bot]
be97d68182 build(deps-dev): bump ruff from 0.1.13 to 0.1.14
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.1.13 to 0.1.14.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.1.13...v0.1.14)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-22 15:02:43 +00:00
aaronatp
f9bceaa3d7 Enable tracebacks on PR build attempts 2024-01-22 04:42:39 -06:00
Moritz
597f449bfa Merge pull request #1935 from mandiant/fix/1886
fix setuptools package discovery
2024-01-22 10:51:40 +01:00
Moritz
b032eec993 Merge pull request #1934 from mandiant/fix/1880
verify target file type and warn user
2024-01-19 09:54:23 +01:00
mr-tz
1a44e899cb verify target file type and warn user 2024-01-18 12:33:28 +01:00
mr-tz
734bfd4ad2 fix setuptools package discovery 2024-01-18 11:56:00 +01:00
Moritz
12b628318d Merge pull request #1930 from mandiant/dependabot/pip/pytest-7.4.4
build(deps-dev): bump pytest from 7.4.3 to 7.4.4
2024-01-18 10:17:21 +01:00
Moritz
be30117030 Merge pull request #1931 from mandiant/dependabot/pip/ruff-0.1.13
build(deps-dev): bump ruff from 0.1.9 to 0.1.13
2024-01-18 10:17:05 +01:00
Capa Bot
6b41e02d63 Sync capa rules submodule 2024-01-17 08:22:01 +00:00
Capa Bot
d2ca130060 Sync capa rules submodule 2024-01-17 08:10:13 +00:00
Moritz
50dcf7ca20 Merge pull request #1932 from mandiant/update-lint-data-20241
update lint data
2024-01-17 09:07:48 +01:00
mr-tz
9bc04ec612 update data via script 2024-01-16 15:29:25 +01:00
dependabot[bot]
966976d97c build(deps-dev): bump ruff from 0.1.9 to 0.1.13
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.1.9 to 0.1.13.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.1.9...v0.1.13)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-15 14:08:54 +00:00
dependabot[bot]
05d7083890 build(deps-dev): bump pytest from 7.4.3 to 7.4.4
Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.4.3 to 7.4.4.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/7.4.3...7.4.4)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-15 14:08:38 +00:00
Willi Ballenthin
1dc72a3183 elf: detect linux via GCC .ident directives (#1928)
* elf: detect linux via GCC .ident directives

* changelog

* pep8
2024-01-11 16:15:26 +01:00
Capa Bot
efc26be196 Sync capa rules submodule 2024-01-11 14:20:33 +00:00
Willi Ballenthin
f3bc132565 render: show human readable flavor name (#1925) 2024-01-11 14:06:39 +01:00
Willi Ballenthin
ad46b33bb7 com: move database into python files (#1924)
* com: move database into python files

* com: pep8 and lints

* com: fix generated string feature type

* pyinstaller: remove reference to old assets directory
2024-01-11 14:06:24 +01:00
dependabot[bot]
9e5cc07a48 build(deps-dev): bump types-tabulate from 0.9.0.3 to 0.9.0.20240106 (#1923)
Bumps [types-tabulate](https://github.com/python/typeshed) from 0.9.0.3 to 0.9.0.20240106.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-tabulate
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-09 12:51:03 -07:00
Moritz
f4fecf43bf Merge pull request #1922 from mandiant/dependabot/pip/types-requests-2.31.0.20240106
build(deps-dev): bump types-requests from 2.31.0.10 to 2.31.0.20240106
2024-01-09 16:20:10 +01:00
Moritz
7426574741 Merge pull request #1921 from mandiant/dependabot/pip/flake8-7.0.0
build(deps-dev): bump flake8 from 6.1.0 to 7.0.0
2024-01-09 16:19:57 +01:00
Moritz
9ab7a24153 Merge pull request #1920 from mandiant/dependabot/pip/wcwidth-0.2.13
build(deps-dev): bump wcwidth from 0.2.12 to 0.2.13
2024-01-09 16:19:42 +01:00
Mike Hunhoff
f37b598010 fix: do not trim api names that include :: (#1897) 2024-01-08 10:59:24 -07:00
dependabot[bot]
5ca59634f3 build(deps-dev): bump types-requests from 2.31.0.10 to 2.31.0.20240106
Bumps [types-requests](https://github.com/python/typeshed) from 2.31.0.10 to 2.31.0.20240106.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-requests
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-08 14:46:29 +00:00
dependabot[bot]
42c1a307f3 build(deps-dev): bump flake8 from 6.1.0 to 7.0.0
Bumps [flake8](https://github.com/pycqa/flake8) from 6.1.0 to 7.0.0.
- [Commits](https://github.com/pycqa/flake8/compare/6.1.0...7.0.0)

---
updated-dependencies:
- dependency-name: flake8
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-08 14:46:23 +00:00
dependabot[bot]
ef5063171b build(deps-dev): bump wcwidth from 0.2.12 to 0.2.13
Bumps [wcwidth](https://github.com/jquast/wcwidth) from 0.2.12 to 0.2.13.
- [Release notes](https://github.com/jquast/wcwidth/releases)
- [Commits](https://github.com/jquast/wcwidth/compare/0.2.12...0.2.13)

---
updated-dependencies:
- dependency-name: wcwidth
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-08 14:46:19 +00:00
Blas
7584e4a5e6 dotnet: emit enclosing class information for nested classes (#1913)
* Update helpers.py

* Update helpers.py

* TypeRef correction in helpers.py

* Fixed TypeRef to proper functionality

* Accounts for TypeRef updated tuple

* Corrected TypeDef tuple creation in helpers.py

* Update types.py

* Update types.py

* Create helpers_draft.py

* Update capa/features/extractors/dnfile/helpers.py

Co-authored-by: Mike Hunhoff <mike.hunhoff@gmail.com>

* Update helper functions, variables, and draft further implementations

* Update helpers.py

* Update types.py

* Directly access TypeDef and TypeRef tables

* Update helpers.py

* Update helpers.py

* Delete capa/features/extractors/dnfile/helpers_draft.py

* Update types.py

* Update dotnetfile.py

* Update types.py comment

* Clean extract_file_class_features in dotnetfile.py

* Cleaned up callers, var names, and other small items

* Update dotnetfile.py

* Clean up caller logic in dotnetfile.py

* Clean up callers and update helper logic in helpers.py

* Linter corrections for types.py

* Linter corrections for dotnetfile.py

* Linter corrections and caller functions cleanup for helpers.py

* Update capa/features/extractors/dnfile/helpers.py

Co-authored-by: Mike Hunhoff <mike.hunhoff@gmail.com>

* Update capa/features/extractors/dnfile/helpers.py

Co-authored-by: Mike Hunhoff <mike.hunhoff@gmail.com>

* Update capa/features/extractors/dnfile/helpers.py

Co-authored-by: Mike Hunhoff <mike.hunhoff@gmail.com>

* Update capa/features/extractors/dnfile/helpers.py

Co-authored-by: Mike Hunhoff <mike.hunhoff@gmail.com>

* Update capa/features/extractors/dnfile/helpers.py

Co-authored-by: Mike Hunhoff <mike.hunhoff@gmail.com>

* Update capa/features/extractors/dnfile/helpers.py

Co-authored-by: Mike Hunhoff <mike.hunhoff@gmail.com>

* Update capa/features/extractors/dnfile/helpers.py

Co-authored-by: Mike Hunhoff <mike.hunhoff@gmail.com>

* Update capa/features/extractors/dnfile/helpers.py

Co-authored-by: Mike Hunhoff <mike.hunhoff@gmail.com>

* Update capa/features/extractors/dnfile/helpers.py

Co-authored-by: Mike Hunhoff <mike.hunhoff@gmail.com>

* Update helpers.py

* Update dotnetfile.py

* Update tuple type in types.py

* Update dotnetfile.py

* Update return value annotations in helpers.py

* Linting update types.py

* Linting update dotnetfile.py

* Added unit tests to fixtures.py

* Update types.py

* Linting fix for types.py

* Update CHANGELOG.md

* Small changes to return types in helpers.py

---------

Co-authored-by: Mike Hunhoff <mike.hunhoff@gmail.com>
2024-01-05 10:09:38 -07:00
Capa Bot
62474c764a Sync capa-testfiles submodule 2024-01-05 14:24:40 +00:00
Capa Bot
1fc26b4f27 Sync capa rules submodule 2024-01-04 13:07:27 +00:00
Capa Bot
037a97381c Sync capa-testfiles submodule 2024-01-04 08:16:43 +00:00
Capa Bot
ef65f14260 Sync capa-testfiles submodule 2024-01-03 16:36:36 +00:00
Capa Bot
3214ecf0ee Sync capa rules submodule 2024-01-03 16:32:40 +00:00
dependabot[bot]
23c5e6797f build(deps-dev): bump ruff from 0.1.7 to 0.1.9 (#1915)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.1.7 to 0.1.9.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.1.7...v0.1.9)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-02 10:31:29 -07:00
dependabot[bot]
e940890c29 build(deps-dev): bump mypy from 1.7.1 to 1.8.0 (#1916)
Bumps [mypy](https://github.com/python/mypy) from 1.7.1 to 1.8.0.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.7.1...v1.8.0)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-02 09:05:49 -07:00
dependabot[bot]
21b76fc91e build(deps-dev): bump setuptools from 69.0.2 to 69.0.3 (#1917)
Bumps [setuptools](https://github.com/pypa/setuptools) from 69.0.2 to 69.0.3.
- [Release notes](https://github.com/pypa/setuptools/releases)
- [Changelog](https://github.com/pypa/setuptools/blob/main/NEWS.rst)
- [Commits](https://github.com/pypa/setuptools/compare/v69.0.2...v69.0.3)

---
updated-dependencies:
- dependency-name: setuptools
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-02 09:05:27 -07:00
dependabot[bot]
05ef952129 build(deps-dev): bump black from 23.12.0 to 23.12.1 (#1918)
Bumps [black](https://github.com/psf/black) from 23.12.0 to 23.12.1.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/23.12.0...23.12.1)

---
updated-dependencies:
- dependency-name: black
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-02 09:05:09 -07:00
Mike Hunhoff
22f4251ad6 ghidra: improve instruction string and bytes feature extraction (#1885)
* ghidra: improve instruction string and bytes feature extraction

* focus on data references only

* remove unneeded check
2023-12-24 18:24:54 -08:00
dependabot[bot]
92478d2469 build(deps-dev): bump black from 23.11.0 to 23.12.0 (#1911)
Bumps [black](https://github.com/psf/black) from 23.11.0 to 23.12.0.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/23.11.0...23.12.0)

---
updated-dependencies:
- dependency-name: black
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-19 12:29:40 -07:00
dependabot[bot]
2aaba6ef16 build(deps-dev): bump isort from 5.13.0 to 5.13.2 (#1910)
Bumps [isort](https://github.com/pycqa/isort) from 5.13.0 to 5.13.2.
- [Release notes](https://github.com/pycqa/isort/releases)
- [Changelog](https://github.com/PyCQA/isort/blob/main/CHANGELOG.md)
- [Commits](https://github.com/pycqa/isort/compare/5.13.0...5.13.2)

---
updated-dependencies:
- dependency-name: isort
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-19 11:04:49 -07:00
dependabot[bot]
8120fb796e build(deps-dev): bump flake8-bugbear from 23.11.26 to 23.12.2 (#1892)
Bumps [flake8-bugbear](https://github.com/PyCQA/flake8-bugbear) from 23.11.26 to 23.12.2.
- [Release notes](https://github.com/PyCQA/flake8-bugbear/releases)
- [Commits](https://github.com/PyCQA/flake8-bugbear/compare/23.11.26...23.12.2)

---
updated-dependencies:
- dependency-name: flake8-bugbear
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-19 11:03:51 -07:00
dependabot[bot]
f3c38ae300 build(deps-dev): bump termcolor from 2.3.0 to 2.4.0 (#1891)
Bumps [termcolor](https://github.com/termcolor/termcolor) from 2.3.0 to 2.4.0.
- [Release notes](https://github.com/termcolor/termcolor/releases)
- [Changelog](https://github.com/termcolor/termcolor/blob/main/CHANGES.md)
- [Commits](https://github.com/termcolor/termcolor/compare/2.3.0...2.4.0)

---
updated-dependencies:
- dependency-name: termcolor
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-19 11:03:39 -07:00
Capa Bot
bf56ee0311 Sync capa rules submodule 2023-12-18 06:54:41 +00:00
Capa Bot
4a84660e76 Sync capa rules submodule 2023-12-18 06:54:07 +00:00
Mike Hunhoff
382c20cd58 ghidra: fix UnboundLocalError exception (#1881) 2023-12-15 17:03:43 -08:00
Mike Hunhoff
2dbac05716 ghidra: fix IndexError exception (#1879)
* ghidra: fix IndexError exception
2023-12-15 16:23:19 -08:00
dependabot[bot]
3f449f3c0f build(deps-dev): bump isort from 5.11.4 to 5.13.0 (#1900)
Bumps [isort](https://github.com/pycqa/isort) from 5.11.4 to 5.13.0.
- [Release notes](https://github.com/pycqa/isort/releases)
- [Changelog](https://github.com/PyCQA/isort/blob/main/CHANGELOG.md)
- [Commits](https://github.com/pycqa/isort/compare/5.11.4...5.13.0)

---
updated-dependencies:
- dependency-name: isort
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-13 15:56:24 +01:00
dependabot[bot]
51b63b465b build(deps-dev): bump ruff from 0.1.6 to 0.1.7 (#1902)
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.1.6 to 0.1.7.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.1.6...v0.1.7)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-13 15:56:16 +01:00
dependabot[bot]
afb3426e96 build(deps-dev): bump pyinstaller from 6.2.0 to 6.3.0 (#1901)
Bumps [pyinstaller](https://github.com/pyinstaller/pyinstaller) from 6.2.0 to 6.3.0.
- [Release notes](https://github.com/pyinstaller/pyinstaller/releases)
- [Changelog](https://github.com/pyinstaller/pyinstaller/blob/develop/doc/CHANGES.rst)
- [Commits](https://github.com/pyinstaller/pyinstaller/compare/v6.2.0...v6.3.0)

---
updated-dependencies:
- dependency-name: pyinstaller
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-13 15:56:04 +01:00
Arnim Rupp
1d3ae1f216 Update capa2yara.py (#1904)
Extend unhandled strings to allow capa2yara to run through
2023-12-13 15:51:56 +01:00
Capa Bot
f229c8ecb8 Sync capa rules submodule 2023-12-13 11:04:32 +00:00
Capa Bot
e3da2d88d0 Sync capa rules submodule 2023-12-11 16:07:10 +00:00
Capa Bot
e4eb4340b1 Sync capa rules submodule 2023-12-09 06:53:06 +00:00
Capa Bot
a8e7611252 Sync capa rules submodule 2023-12-08 21:41:12 +00:00
aaronatp
8531acd7c5 Only show stack trace in debug mode (#1860)
* Only show stack trace in dev mode

* Update custom exception handler to handle KeyboardInterrupts
2023-12-08 22:07:16 +01:00
Mike Hunhoff
d6f7d2180f dotnet: combine dnfile_.py and dotnetfile.py (#1895) 2023-12-07 14:06:54 -07:00
Moritz
d1b213aaac Merge pull request #1890 from mandiant/fix-dlls
fix symbol generation, ordinals
2023-12-03 21:05:01 +01:00
mr-tz
51ddadbc87 fix symbol generation, ordinals 2023-12-03 17:49:54 +02:00
Moritz
cd52b1937b Merge pull request #1887 from mandiant/fix/dynamic/1882
dynamic: fix UnboundLocalError exception
2023-12-01 14:52:55 +01:00
Mike Hunhoff
ca14dab804 dynamic: fix UnboundLocalError exception 2023-11-30 14:52:18 -07:00
Moritz
fbe0440361 add build for Python 3.11 for linux (#1877)
* add build for Python 3.11 for linux
2023-11-29 22:42:56 +01:00
Moritz
4c3586b5e9 Merge pull request #1697 from mandiant/dynamic-feature-extraction
add dynamic analysis
2023-11-29 17:45:24 +01:00
mr-tz
47019e4d7c Merge branch 'master' into dynamic-feature-extraction 2023-11-29 16:28:12 +01:00
Capa Bot
a236a952bc Sync capa rules submodule 2023-11-29 15:24:54 +00:00
mr-tz
73ea822123 Merge branch 'master' into dynamic-feature-extraction 2023-11-29 16:17:09 +01:00
Willi Ballenthin
3c159a1f52 ci: revert temporary CI event subscription 2023-11-29 14:26:53 +00:00
Capa Bot
7db40c3af8 Sync capa rules submodule 2023-11-29 13:53:18 +00:00
Willi Ballenthin
9a996d07c7 Merge branch 'dynamic-feature-extraction' of public.github.com:mandiant/capa into dynamic-feature-extraction 2023-11-29 13:46:47 +00:00
Willi Ballenthin
93cfb6ef8c sync testfiles submodule 2023-11-29 13:46:29 +00:00
Capa Bot
a29c320f95 Sync capa-testfiles submodule 2023-11-29 13:45:44 +00:00
Capa Bot
277d7e0687 Sync capa rules submodule 2023-11-29 13:33:01 +00:00
Yacine
e66c2efcf5 add documentation for dynamic capa capabilties (#1837)
* README: adapt for dynamic capa

* README.md: fix duplication error

* Update README.md

Co-authored-by: Moritz <mr-tz@users.noreply.github.com>

* documentation: add review suggestions

* documentation: newline fix

* Update README.md

Co-authored-by: Moritz <mr-tz@users.noreply.github.com>

* Update README.md

Co-authored-by: Moritz <mr-tz@users.noreply.github.com>

* Update README.md

Co-authored-by: Moritz <mr-tz@users.noreply.github.com>

---------

Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-11-29 14:26:29 +01:00
Willi Ballenthin
583f8b5688 Merge branch 'dynamic-feature-extraction' of public.github.com:mandiant/capa into dynamic-feature-extraction 2023-11-29 13:13:04 +00:00
Willi Ballenthin
b4c6bf859e changelog 2023-11-29 13:12:30 +00:00
Moritz
ba9da0dd82 Merge pull request #1876 from mandiant/fix/1867
set os, arch, format in meta table
2023-11-29 13:44:43 +01:00
mr-tz
92770dd5c7 set os, arch, format in meta table 2023-11-28 17:09:14 +01:00
Moritz
8946cb633e Merge pull request #1874 from mandiant/fix/global-features
only check and display file limitation once
2023-11-28 15:19:10 +01:00
mr-tz
8f0eb5676e only check and display file limitation once 2023-11-28 15:00:47 +01:00
Willi Ballenthin
cb1a037502 Merge pull request #1869 from mandiant/dependabot/pip/flake8-encodings-0.5.1
build(deps-dev): bump flake8-encodings from 0.5.0.post1 to 0.5.1
2023-11-28 12:38:19 +00:00
dependabot[bot]
c8d0071443 build(deps-dev): bump flake8-encodings from 0.5.0.post1 to 0.5.1
Bumps [flake8-encodings](https://github.com/python-formate/flake8-encodings) from 0.5.0.post1 to 0.5.1.
- [Release notes](https://github.com/python-formate/flake8-encodings/releases)
- [Commits](https://github.com/python-formate/flake8-encodings/compare/v0.5.0.post1...v0.5.1)

---
updated-dependencies:
- dependency-name: flake8-encodings
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-28 12:37:42 +00:00
Willi Ballenthin
e6b8a3e505 Merge pull request #1870 from mandiant/dependabot/pip/wcwidth-0.2.12
build(deps-dev): bump wcwidth from 0.2.10 to 0.2.12
2023-11-28 12:37:16 +00:00
Willi Ballenthin
f328df1bc4 Merge pull request #1871 from mandiant/dependabot/pip/setuptools-69.0.2
build(deps-dev): bump setuptools from 68.0.0 to 69.0.2
2023-11-28 12:37:06 +00:00
Willi Ballenthin
d1aa1557b2 Merge pull request #1872 from mandiant/dependabot/pip/flake8-bugbear-23.11.26
build(deps-dev): bump flake8-bugbear from 23.9.16 to 23.11.26
2023-11-28 12:36:58 +00:00
Willi Ballenthin
a0929124ec Merge pull request #1873 from mandiant/dependabot/pip/mypy-1.7.1
build(deps-dev): bump mypy from 1.7.0 to 1.7.1
2023-11-28 12:36:47 +00:00
dependabot[bot]
84ed6c8d24 build(deps-dev): bump mypy from 1.7.0 to 1.7.1
Bumps [mypy](https://github.com/python/mypy) from 1.7.0 to 1.7.1.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.7.0...v1.7.1)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-27 14:56:45 +00:00
dependabot[bot]
61c8e30f65 build(deps-dev): bump flake8-bugbear from 23.9.16 to 23.11.26
Bumps [flake8-bugbear](https://github.com/PyCQA/flake8-bugbear) from 23.9.16 to 23.11.26.
- [Release notes](https://github.com/PyCQA/flake8-bugbear/releases)
- [Commits](https://github.com/PyCQA/flake8-bugbear/compare/23.9.16...23.11.26)

---
updated-dependencies:
- dependency-name: flake8-bugbear
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-27 14:56:29 +00:00
dependabot[bot]
6a4994f1ef build(deps-dev): bump setuptools from 68.0.0 to 69.0.2
Bumps [setuptools](https://github.com/pypa/setuptools) from 68.0.0 to 69.0.2.
- [Release notes](https://github.com/pypa/setuptools/releases)
- [Changelog](https://github.com/pypa/setuptools/blob/main/NEWS.rst)
- [Commits](https://github.com/pypa/setuptools/compare/v68.0.0...v69.0.2)

---
updated-dependencies:
- dependency-name: setuptools
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-27 14:56:01 +00:00
dependabot[bot]
fce105060d build(deps-dev): bump wcwidth from 0.2.10 to 0.2.12
Bumps [wcwidth](https://github.com/jquast/wcwidth) from 0.2.10 to 0.2.12.
- [Release notes](https://github.com/jquast/wcwidth/releases)
- [Commits](https://github.com/jquast/wcwidth/compare/0.2.10...0.2.12)

---
updated-dependencies:
- dependency-name: wcwidth
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-27 14:55:45 +00:00
Moritz
d84457eac7 Merge pull request #1868 from mandiant/fix/global-features
Fix global features and display
2023-11-27 14:06:01 +01:00
mr-tz
890c879e7c only check and display file limitation once 2023-11-27 13:28:36 +01:00
mr-tz
f201ef1d22 actually get global feature values 2023-11-27 13:28:06 +01:00
Moritz
f763d14266 Merge pull request #1862 from mandiant/dependabot/pip/wcwidth-0.2.10
build(deps-dev): bump wcwidth from 0.2.9 to 0.2.10
2023-11-23 12:28:16 +01:00
Moritz
6f0be06f86 Merge pull request #1861 from mandiant/dependabot/pip/ruff-0.1.6
build(deps-dev): bump ruff from 0.1.5 to 0.1.6
2023-11-23 12:28:05 +01:00
Capa Bot
347687579c Sync capa rules submodule 2023-11-22 18:05:52 +00:00
Capa Bot
d61d1dc591 Sync capa rules submodule 2023-11-22 13:10:44 +00:00
Capa Bot
235a3bede0 Sync capa rules submodule 2023-11-21 10:52:38 +00:00
dependabot[bot]
cf35d2c497 build(deps-dev): bump wcwidth from 0.2.9 to 0.2.10
Bumps [wcwidth](https://github.com/jquast/wcwidth) from 0.2.9 to 0.2.10.
- [Release notes](https://github.com/jquast/wcwidth/releases)
- [Commits](https://github.com/jquast/wcwidth/compare/0.2.9...0.2.10)

---
updated-dependencies:
- dependency-name: wcwidth
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-20 14:20:59 +00:00
dependabot[bot]
f6048b9e99 build(deps-dev): bump ruff from 0.1.5 to 0.1.6
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.1.5 to 0.1.6.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.1.5...v0.1.6)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-20 14:20:47 +00:00
Capa Bot
9d1e60d4a2 Sync capa-testfiles submodule 2023-11-20 11:40:22 +00:00
Capa Bot
fb1235d26f Sync capa rules submodule 2023-11-20 10:27:11 +00:00
Capa Bot
3fe2328bd2 Sync capa rules submodule 2023-11-17 23:27:52 +00:00
Willi Ballenthin
647abb669f Merge pull request #1858 from doomedraven/patch-1 2023-11-16 14:16:16 +01:00
doomedraven
a5e1eca8cc Create pip-audit.yml 2023-11-16 13:27:25 +01:00
Willi Ballenthin
fdb96709ae Merge pull request #1856 from doomedraven/patch-1
fix pydantic vuln (ReDoS)
2023-11-16 13:20:01 +01:00
doomedraven
490271e50b fix pydantic vuln (ReDoS)
Regular Expression Denial of Service (ReDoS)
MEDIUM SEVERITY
Package Manager: pip
Vulnerable module: pydantic
Remediation
Upgrade pydantic to version 1.10.13, 2.4.0 or higher.
2023-11-16 10:54:59 +01:00
Willi Ballenthin
a870c92a2f sync submodule rules 2023-11-15 11:00:51 +00:00
Willi Ballenthin
de5f08871e sync submodule rules 2023-11-15 10:57:16 +00:00
Capa Bot
2f60ec03af Sync capa rules submodule 2023-11-15 09:25:02 +00:00
Willi Ballenthin
987eb2d358 sync rules submodule 2023-11-14 14:34:08 +00:00
Willi Ballenthin
6e3fff4bae use latest rules migration 2023-11-14 14:29:34 +00:00
Willi Ballenthin
a705bf9eab Merge pull request #1825 from mandiant/fix/issue-1816
verbose: show process name and other human-level details
2023-11-14 12:33:41 +01:00
Willi Ballenthin
c68c68d5cb Merge branch 'dynamic-feature-extraction' into fix/issue-1816 2023-11-14 11:36:24 +01:00
Willi Ballenthin
82013f0e24 submodule: tests: data: sync 2023-11-14 10:35:18 +00:00
Willi Ballenthin
210a13d94e Merge pull request #1850 from mandiant/dependabot/pip/mypy-1.7.0
build(deps-dev): bump mypy from 1.6.1 to 1.7.0
2023-11-14 11:29:59 +01:00
dependabot[bot]
0d5ff45c76 build(deps-dev): bump mypy from 1.6.1 to 1.7.0
Bumps [mypy](https://github.com/python/mypy) from 1.6.1 to 1.7.0.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.6.1...v1.7.0)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-14 10:29:20 +00:00
Willi Ballenthin
11b98cb0b1 Merge pull request #1849 from mandiant/dependabot/pip/black-23.11.0
build(deps-dev): bump black from 23.10.1 to 23.11.0
2023-11-14 11:29:12 +01:00
dependabot[bot]
3c9ab63521 build(deps-dev): bump black from 23.10.1 to 23.11.0
Bumps [black](https://github.com/psf/black) from 23.10.1 to 23.11.0.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/23.10.1...23.11.0)

---
updated-dependencies:
- dependency-name: black
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-14 10:29:05 +00:00
Willi Ballenthin
a2fde921aa Merge pull request #1848 from mandiant/dependabot/pip/ruff-0.1.5
build(deps-dev): bump ruff from 0.1.4 to 0.1.5
2023-11-14 11:28:25 +01:00
Willi Ballenthin
d4f7c77be8 Merge pull request #1847 from mandiant/dependabot/pip/pyinstaller-6.2.0
build(deps-dev): bump pyinstaller from 6.1.0 to 6.2.0
2023-11-14 11:28:08 +01:00
dependabot[bot]
f0f95824ac build(deps-dev): bump ruff from 0.1.4 to 0.1.5
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.1.4 to 0.1.5.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.1.4...v0.1.5)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-13 14:21:13 +00:00
dependabot[bot]
0ba5c23847 build(deps-dev): bump pyinstaller from 6.1.0 to 6.2.0
Bumps [pyinstaller](https://github.com/pyinstaller/pyinstaller) from 6.1.0 to 6.2.0.
- [Release notes](https://github.com/pyinstaller/pyinstaller/releases)
- [Changelog](https://github.com/pyinstaller/pyinstaller/blob/develop/doc/CHANGES.rst)
- [Commits](https://github.com/pyinstaller/pyinstaller/compare/v6.1.0...v6.2.0)

---
updated-dependencies:
- dependency-name: pyinstaller
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-13 14:20:52 +00:00
Moritz
dee0aa73eb Merge pull request #1844 from mandiant/mr-tz-patch-1
fix whitespace removal in format check
2023-11-11 19:53:44 +01:00
Moritz
41a397661f fix whitespace removal in format check 2023-11-10 11:40:55 +01:00
Moritz
52997e70a0 fix imports according to ruff 2023-11-08 16:58:40 +01:00
Moritz
1acc2d1959 Merge branch 'dynamic-feature-extraction' into fix/issue-1816 2023-11-08 16:56:05 +01:00
Moritz
74f70856a6 Merge pull request #1840 from mandiant/dependabot/pip/wcwidth-0.2.9
build(deps-dev): bump wcwidth from 0.2.8 to 0.2.9
2023-11-08 15:38:27 +01:00
Moritz
e5b7ee96fc Merge pull request #1839 from mandiant/dependabot/pip/black-23.10.1
build(deps-dev): bump black from 23.10.0 to 23.10.1
2023-11-08 15:38:02 +01:00
Moritz
92d43f5327 Merge pull request #1838 from mandiant/dependabot/pip/ruamel-yaml-0.18.5
build(deps-dev): bump ruamel-yaml from 0.18.3 to 0.18.5
2023-11-08 15:37:31 +01:00
dependabot[bot]
48abd297a8 build(deps-dev): bump black from 23.10.0 to 23.10.1
Bumps [black](https://github.com/psf/black) from 23.10.0 to 23.10.1.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/23.10.0...23.10.1)

---
updated-dependencies:
- dependency-name: black
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-07 13:16:09 +00:00
Willi Ballenthin
d64a10a287 Merge pull request #1841 from mandiant/dependabot/pip/ruff-0.1.4
build(deps-dev): bump ruff from 0.0.291 to 0.1.4
2023-11-07 14:15:24 +01:00
dependabot[bot]
abf83fe8cf build(deps-dev): bump ruff from 0.0.291 to 0.1.4
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.0.291 to 0.1.4.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.0.291...v0.1.4)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-06 14:42:18 +00:00
dependabot[bot]
6380d936ae build(deps-dev): bump wcwidth from 0.2.8 to 0.2.9
Bumps [wcwidth](https://github.com/jquast/wcwidth) from 0.2.8 to 0.2.9.
- [Release notes](https://github.com/jquast/wcwidth/releases)
- [Commits](https://github.com/jquast/wcwidth/compare/0.2.8...0.2.9)

---
updated-dependencies:
- dependency-name: wcwidth
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-06 14:42:06 +00:00
dependabot[bot]
18ab8d28d9 build(deps-dev): bump ruamel-yaml from 0.18.3 to 0.18.5
Bumps [ruamel-yaml]() from 0.18.3 to 0.18.5.

---
updated-dependencies:
- dependency-name: ruamel-yaml
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-06 14:41:55 +00:00
Willi Ballenthin
a52af3895a verbose: remove TODOs 2023-11-06 10:37:22 +00:00
Willi Ballenthin
5d31bc462b verbose: render dynamic match locations 2023-11-06 10:34:26 +00:00
Willi Ballenthin
7678897334 tests: fix render tests 2023-11-06 10:32:44 +00:00
Willi Ballenthin
75ff58edaa vverbose: better render pid/tid/call index 2023-11-06 10:09:23 +00:00
Willi Ballenthin
eb12ec43f0 mypy 2023-11-06 09:52:00 +00:00
Willi Ballenthin
f7c72cd1c3 vverbose: don't repeat rendered calls when in call scope 2023-11-06 09:52:00 +00:00
Willi Ballenthin
0da614aa4f vverbose: dynamic: show rendered matching API call 2023-11-06 09:52:00 +00:00
Willi Ballenthin
9c81ccf88a vverbose: make missing names an error 2023-11-06 09:52:00 +00:00
Willi Ballenthin
c141f7ec6e verbose: better render scopes 2023-11-06 09:52:00 +00:00
Willi Ballenthin
274a710bb1 report: better compute dynamic layout 2023-11-06 09:52:00 +00:00
Willi Ballenthin
4a7e488e4c Update capa/render/vverbose.py
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2023-11-01 12:19:13 +01:00
Willi Ballenthin
348120dea9 Merge pull request #1835 from mandiant/dependabot/pip/ruamel-yaml-0.18.3
build(deps-dev): bump ruamel-yaml from 0.17.35 to 0.18.3
2023-11-01 12:17:22 +01:00
Willi Ballenthin
435eea1b80 Merge pull request #1834 from mandiant/dependabot/pip/pytest-7.4.3
build(deps-dev): bump pytest from 7.4.2 to 7.4.3
2023-11-01 12:17:12 +01:00
Willi Ballenthin
621d42a093 Merge pull request #1831 from mandiant/dependabot/pip/flake8-no-implicit-concat-0.3.5
build(deps-dev): bump flake8-no-implicit-concat from 0.3.4 to 0.3.5
2023-11-01 12:17:04 +01:00
Willi Ballenthin
15701c6d12 Merge pull request #1829 from mandiant/dependabot/pip/mypy-1.6.1
build(deps-dev): bump mypy from 1.6.0 to 1.6.1
2023-11-01 12:16:55 +01:00
Willi Ballenthin
ec7fc86dc5 Merge pull request #1828 from mandiant/dependabot/pip/types-requests-2.31.0.10
build(deps-dev): bump types-requests from 2.31.0.2 to 2.31.0.10
2023-11-01 12:16:46 +01:00
dependabot[bot]
8d55c2f249 build(deps-dev): bump ruamel-yaml from 0.17.35 to 0.18.3
Bumps [ruamel-yaml]() from 0.17.35 to 0.18.3.

---
updated-dependencies:
- dependency-name: ruamel-yaml
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-30 14:11:50 +00:00
dependabot[bot]
66607f1412 build(deps-dev): bump pytest from 7.4.2 to 7.4.3
Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.4.2 to 7.4.3.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/7.4.2...7.4.3)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-30 14:11:00 +00:00
Yacine
0097822e51 Merge pull request #1820 from yelhamer/capabilities-module
add a capabilities module
2023-10-27 13:39:49 +02:00
Yacine Elhamer
e559cc27d5 capa.rules: remove redundant ceng.MatchResults import 2023-10-26 19:43:26 +02:00
Yacine Elhamer
a0cec3f07d capa.rules: remove redundant is_internal_rule() and has_file_limitations() from capa source code 2023-10-26 19:41:09 +02:00
dependabot[bot]
874faf0901 build(deps-dev): bump mypy from 1.6.0 to 1.6.1
Bumps [mypy](https://github.com/python/mypy) from 1.6.0 to 1.6.1.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.6.0...v1.6.1)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-24 19:48:35 +00:00
Moritz
4750913fad Merge pull request #1827 from mandiant/dependabot/pip/black-23.10.0
build(deps-dev): bump black from 23.9.1 to 23.10.0
2023-10-24 21:47:52 +02:00
dependabot[bot]
e7198b2aaf build(deps-dev): bump flake8-no-implicit-concat from 0.3.4 to 0.3.5
Bumps [flake8-no-implicit-concat](https://github.com/10sr/flake8-no-implicit-concat) from 0.3.4 to 0.3.5.
- [Release notes](https://github.com/10sr/flake8-no-implicit-concat/releases)
- [Changelog](https://github.com/10sr/flake8-no-implicit-concat/blob/master/CHANGELOG.md)
- [Commits](https://github.com/10sr/flake8-no-implicit-concat/compare/v0.3.4...v0.3.5)

---
updated-dependencies:
- dependency-name: flake8-no-implicit-concat
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-23 14:47:26 +00:00
dependabot[bot]
426931c392 build(deps-dev): bump types-requests from 2.31.0.2 to 2.31.0.10
Bumps [types-requests](https://github.com/python/typeshed) from 2.31.0.2 to 2.31.0.10.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-requests
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-23 14:47:03 +00:00
dependabot[bot]
fec1e6a947 build(deps-dev): bump black from 23.9.1 to 23.10.0
Bumps [black](https://github.com/psf/black) from 23.9.1 to 23.10.0.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/23.9.1...23.10.0)

---
updated-dependencies:
- dependency-name: black
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-23 14:46:59 +00:00
Moritz
db53424548 Merge pull request #1826 from mandiant/fix-model-hexint
fix parsing base 10/16
2023-10-23 09:02:21 +02:00
Yacine Elhamer
8029fed31c Merge branch 'capabilities-module' of https://github.com/yelhamer/capa into capabilities-module 2023-10-20 20:11:28 +02:00
Yacine Elhamer
3572b512d9 test_capabilities.py: add missing test_com_feature_matching() test 2023-10-20 20:11:08 +02:00
Yacine Elhamer
ab06c94d80 capa/main.py: move has_rule_with_namespace() to capa.rules.RuleSet 2023-10-20 20:10:29 +02:00
Willi Ballenthin
9e6919f33c layout: capture call names
so that they can be rendered to output
2023-10-20 14:21:13 +00:00
mr-tz
99042f232d fix parsing base 10/16 2023-10-20 15:26:11 +02:00
Willi Ballenthin
393b0e63f0 layout: capture process name 2023-10-20 12:39:28 +00:00
Willi Ballenthin
ee4f02908c layout: capture process name 2023-10-20 12:38:35 +00:00
Moritz
c9df78252a Ignore DLL names for API features (#1824)
* ignore DLL name for api features

* keep DLL name for import features

* fix tests
2023-10-20 13:39:15 +02:00
Willi Ballenthin
788251ba2b vverbose: render scope for humans 2023-10-20 11:37:42 +00:00
Willi Ballenthin
62d4b008c5 Merge pull request #1822 from mandiant/fix/dynamic-freeze
update freeze for dynamic
2023-10-20 13:16:48 +02:00
Capa Bot
be6f87318e Sync capa rules submodule 2023-10-20 09:50:07 +00:00
Yacine Elhamer
aae72667a3 Merge branch 'capabilities-module' of https://github.com/yelhamer/capa into capabilities-module 2023-10-20 10:16:41 +02:00
Yacine Elhamer
d6c5d98b0d move is_file_limitation_rule() to the rules module (Rule class) 2023-10-20 10:16:09 +02:00
Yacine Elhamer
d5ae2ffd91 capa.capabilities: move has_file_limitations() from capa.main to the capabilities module 2023-10-20 10:15:20 +02:00
Yacine Elhamer
96fb204d9d move capa.features.capabilities to capa.capabilities, and update scripts 2023-10-20 09:54:24 +02:00
Yacine
20604c4b41 Update capa/capabilities/static.py
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-10-20 09:28:13 +02:00
Yacine
423d942bd0 Update capa/capabilities/dynamic.py
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-10-20 09:28:05 +02:00
Yacine
f9b87417e6 Update capa/capabilities/common.py
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-10-20 09:27:58 +02:00
Willi Ballenthin
fc4618e234 Merge branch 'dynamic-feature-extraction' into fix/dynamic-freeze 2023-10-20 09:16:07 +02:00
Willi Ballenthin
1143f2ba56 changelog 2023-10-20 07:11:42 +00:00
Willi Ballenthin
10dc4b92b1 freeze: update freeze format v3 2023-10-20 06:59:53 +00:00
Willi Ballenthin
bfecf414fb freeze: add dynamic tests 2023-10-20 06:59:34 +00:00
Willi Ballenthin
0231ceef87 null extractor: fix typings 2023-10-20 06:59:16 +00:00
Yacine
0ae8f34aff Merge branch 'dynamic-feature-extraction' into capabilities-module 2023-10-20 08:55:49 +02:00
Moritz
b8b55f4e19 identify potential JSON object data start (#1819)
* identify potential JSON object data start
2023-10-19 17:17:57 +02:00
Willi Ballenthin
d42829d7e7 Merge pull request #1765 from mandiant/fix/dynamic-proto
protobuf: add dynamic support
2023-10-19 13:37:45 +02:00
Willi Ballenthin
c724a4b311 ci: only run BN and Ghidra tests after others complete
these are much less likely to fail because they're
changed less often, so don't run them until we know
other tests also pass.
2023-10-19 11:35:42 +00:00
Willi Ballenthin
84e22b187d doc 2023-10-19 11:29:30 +00:00
Willi Ballenthin
b6a0d6e1f3 pre-commit: fix stages 2023-10-19 11:26:22 +00:00
Willi Ballenthin
1cb3ca61cd pre-commit: only run fast checks during commit 2023-10-19 10:35:57 +00:00
Willi Ballenthin
288313a300 changelog 2023-10-19 10:28:37 +00:00
Willi Ballenthin
2cc6a37713 ci: run fast tests before the full suite 2023-10-19 10:23:03 +00:00
Willi Ballenthin
fbeb33a91f Merge branch 'dynamic-feature-extraction' into fix/dynamic-proto 2023-10-19 10:05:26 +00:00
Willi Ballenthin
3519125e03 tests: fix COM tests with dynamic scope 2023-10-19 10:04:26 +00:00
Willi Ballenthin
98360328f9 proto: fix serialization of call address 2023-10-19 09:59:18 +00:00
Willi Ballenthin
3d4facd9a3 Merge branch 'dynamic-feature-extraction' into fix/dynamic-proto 2023-10-19 09:24:37 +00:00
Willi Ballenthin
8b0ba1e656 tests: rename freeze tests 2023-10-19 09:24:18 +00:00
Willi Ballenthin
7bc3fba7b0 Merge branch 'dynamic-feature-extraction' into fix/dynamic-proto 2023-10-19 09:20:15 +00:00
Willi Ballenthin
d5e187bc70 Merge branch 'master' into dynamic-feature-extraction 2023-10-19 09:15:57 +00:00
Yacine Elhamer
85610a82c5 changelog fix 2023-10-19 10:59:45 +02:00
Yacine Elhamer
f2011c162c fix styling issues 2023-10-19 10:58:30 +02:00
Yacine Elhamer
37caeb2736 capabilities: add a test file for the new capabilities module, and move the corresponding tests from main to there 2023-10-19 10:54:53 +02:00
Yacine Elhamer
5c48f38208 capa/main.py: add a capabilities module and move all of the capability extraction there 2023-10-19 10:39:14 +02:00
Moritz
8687c740d5 Merge pull request #1817 from mandiant/improve-vv-render
improve vverbose rendering
2023-10-19 09:41:31 +02:00
Yacine
9609d63f8a Update tests/test_main.py
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2023-10-19 08:10:29 +02:00
Capa Bot
772f806eb6 Sync capa rules submodule 2023-10-18 15:01:37 +00:00
Willi Ballenthin
5eaba611d1 Merge pull request #1738 from Aayush-Goel-04/Aayush-Goel-04/Issue#322
add com class/interface features
2023-10-18 17:00:39 +02:00
mr-tz
b6f13f3489 improve vverbose rendering 2023-10-18 13:37:56 +02:00
Aayush Goel
178cfce456 Merge branch 'Aayush-Goel-04/Issue#322' of https://github.com/Aayush-Goel-04/capa into Aayush-Goel-04/Issue#322 2023-10-18 16:33:37 +05:30
Aayush Goel
94cf53a1e3 Update __init__.py 2023-10-18 16:33:31 +05:30
Moritz
2cfd45022a improve and fix various dynamic parts (#1809)
* improve and fix various dynamic parts
2023-10-18 10:59:41 +02:00
Aayush Goel
26a2d1b4d1 Merge branch 'mandiant:master' into Aayush-Goel-04/Issue#322 2023-10-17 21:09:07 +05:30
Aayush Goel
6dbd3768ce Update __init__.py 2023-10-17 21:04:21 +05:30
Willi Ballenthin
21f9e0736d isort 2023-10-17 15:07:34 +00:00
Aayush Goel
7cd5aa1c40 Added Enum for comType 2023-10-17 20:28:49 +05:30
Willi Ballenthin
55e4fddc51 mypy 2023-10-17 14:46:33 +00:00
Willi Ballenthin
1aac4a1a69 mypy 2023-10-17 14:42:58 +00:00
Willi Ballenthin
92daf3a530 elffile: fix property access 2023-10-17 14:28:52 +00:00
Willi Ballenthin
547502051f dynamic: fix tests 2023-10-17 14:27:36 +00:00
Aayush Goel
884b714be2 loading com db only once
avoid loading db multiple times by caching it.
2023-10-17 19:48:06 +05:30
Willi Ballenthin
7205bc26ef submodule: rules: update 2023-10-17 12:28:45 +00:00
Willi Ballenthin
e1b3a3f6b4 rules: fix rendering of yaml 2023-10-17 12:22:32 +00:00
Willi Ballenthin
cb5fa36fc8 flake8 2023-10-17 11:44:48 +00:00
Willi Ballenthin
8ee97acf2a dynamic: fix some tests 2023-10-17 11:43:09 +00:00
Willi Ballenthin
44d05f9498 dynamic: fix some tests 2023-10-17 11:41:40 +00:00
Willi Ballenthin
bf233c1c7a integrate Ghidra backend with dynamic analysis 2023-10-17 10:56:35 +00:00
Willi Ballenthin
182a9868ca merge master 2023-10-17 10:32:25 +00:00
Willi Ballenthin
40d9587fa4 Merge pull request #1808 from mandiant/dependabot/pip/ruamel-yaml-0.17.35
build(deps-dev): bump ruamel-yaml from 0.17.32 to 0.17.35
2023-10-17 09:59:41 +02:00
Willi Ballenthin
430fdb074b Merge pull request #1807 from mandiant/dependabot/pip/pre-commit-3.5.0
build(deps-dev): bump pre-commit from 3.4.0 to 3.5.0
2023-10-17 09:59:30 +02:00
Willi Ballenthin
0324d24490 Merge pull request #1806 from mandiant/dependabot/pip/flake8-simplify-0.21.0
build(deps-dev): bump flake8-simplify from 0.20.0 to 0.21.0
2023-10-17 09:59:21 +02:00
Willi Ballenthin
41c286d1a3 Merge pull request #1805 from mandiant/dependabot/pip/pyinstaller-6.1.0
build(deps-dev): bump pyinstaller from 6.0.0 to 6.1.0
2023-10-17 09:59:13 +02:00
Willi Ballenthin
187cf40d6f Merge pull request #1804 from mandiant/dependabot/pip/mypy-1.6.0
build(deps-dev): bump mypy from 1.5.1 to 1.6.0
2023-10-17 09:58:44 +02:00
Capa Bot
c37a0e525c Sync capa rules submodule 2023-10-16 14:53:14 +00:00
dependabot[bot]
de0c35b6ad build(deps-dev): bump ruamel-yaml from 0.17.32 to 0.17.35
Bumps [ruamel-yaml](https://sourceforge.net/p/ruamel-yaml/code/ci/default/tree) from 0.17.32 to 0.17.35.

---
updated-dependencies:
- dependency-name: ruamel-yaml
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-16 14:18:33 +00:00
dependabot[bot]
d99b454c0e build(deps-dev): bump pre-commit from 3.4.0 to 3.5.0
Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 3.4.0 to 3.5.0.
- [Release notes](https://github.com/pre-commit/pre-commit/releases)
- [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md)
- [Commits](https://github.com/pre-commit/pre-commit/compare/v3.4.0...v3.5.0)

---
updated-dependencies:
- dependency-name: pre-commit
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-16 14:18:11 +00:00
dependabot[bot]
44f156925a build(deps-dev): bump flake8-simplify from 0.20.0 to 0.21.0
Bumps [flake8-simplify](https://github.com/MartinThoma/flake8-simplify) from 0.20.0 to 0.21.0.
- [Release notes](https://github.com/MartinThoma/flake8-simplify/releases)
- [Changelog](https://github.com/MartinThoma/flake8-simplify/blob/main/CHANGELOG.md)
- [Commits](https://github.com/MartinThoma/flake8-simplify/commits/0.21.0)

---
updated-dependencies:
- dependency-name: flake8-simplify
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-16 14:17:47 +00:00
dependabot[bot]
599c115767 build(deps-dev): bump pyinstaller from 6.0.0 to 6.1.0
Bumps [pyinstaller](https://github.com/pyinstaller/pyinstaller) from 6.0.0 to 6.1.0.
- [Release notes](https://github.com/pyinstaller/pyinstaller/releases)
- [Changelog](https://github.com/pyinstaller/pyinstaller/blob/develop/doc/CHANGES.rst)
- [Commits](https://github.com/pyinstaller/pyinstaller/compare/v6.0.0...v6.1.0)

---
updated-dependencies:
- dependency-name: pyinstaller
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-16 14:17:36 +00:00
dependabot[bot]
6ecc9b77b9 build(deps-dev): bump mypy from 1.5.1 to 1.6.0
Bumps [mypy](https://github.com/python/mypy) from 1.5.1 to 1.6.0.
- [Commits](https://github.com/python/mypy/compare/v1.5.1...v1.6.0)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-16 14:17:01 +00:00
Aayush Goel
412d296d6b Merge branch 'mandiant:master' into Aayush-Goel-04/Issue#322 2023-10-16 16:38:18 +05:30
Aayush Goel
db32d90480 tests updated 2023-10-16 16:35:30 +05:30
Yacine Elhamer
9a66c265db cape/file.py: fix flake8 issue of using '+' for logging 2023-10-16 12:11:07 +02:00
Yacine Elhamer
a1aca3aeb3 Merge branch 'dynamic-feature-extraction' of https://github.com/mandiant/capa into dynamic-feature-extraction 2023-10-16 12:04:47 +02:00
Yacine Elhamer
ffe6ab6842 main.py: load signatures only for the static context 2023-10-16 12:04:38 +02:00
Yacine
d1b7afbe13 Update capa/render/verbose.py
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2023-10-14 09:36:55 +02:00
Capa Bot
77de088ac9 Sync capa rules submodule 2023-10-12 09:01:30 +00:00
Capa Bot
40ba6679f0 Sync capa-testfiles submodule 2023-10-11 14:36:05 +00:00
Moritz
8b6fa35e9f Merge pull request #1794 from mandiant/dependabot/pip/pyinstaller-6.0.0
build(deps-dev): bump pyinstaller from 5.10.1 to 6.0.0
2023-10-11 13:58:48 +02:00
Moritz
f85ea915bf Update pyinstaller.spec 2023-10-11 12:29:18 +02:00
Moritz
312ad48041 Merge pull request #1801 from mandiant/dependabot/pip/dnfile-0.14.1
build(deps-dev): bump dnfile from 0.13.0 to 0.14.1
2023-10-11 12:20:07 +02:00
Moritz
65b80d4d13 Merge pull request #1800 from mandiant/dependabot/pip/flake8-bugbear-23.9.16
build(deps-dev): bump flake8-bugbear from 23.7.10 to 23.9.16
2023-10-11 12:19:51 +02:00
Moritz
fb098fde5f Merge pull request #1799 from mandiant/dependabot/pip/black-23.9.1
build(deps-dev): bump black from 23.7.0 to 23.9.1
2023-10-11 12:19:36 +02:00
Moritz
eedec933c2 Merge pull request #1798 from mandiant/dependabot/pip/wcwidth-0.2.8
build(deps-dev): bump wcwidth from 0.2.6 to 0.2.8
2023-10-11 12:19:20 +02:00
Yacine Elhamer
559f2fd162 cape/file.py: flake8 fixes 2023-10-11 11:56:49 +02:00
Yacine Elhamer
953b2e82d2 rendering: several fixes and added types/classes 2023-10-11 11:52:16 +02:00
Capa Bot
cd268d6327 Sync capa rules submodule 2023-10-10 13:34:52 +00:00
Aayush Goel
23ecb248a5 Update __init__.py 2023-10-10 18:08:07 +05:30
Aayush Goel
bc165331db Update __init__.py 2023-10-10 17:56:18 +05:30
Capa Bot
5d66a389d3 Sync capa rules submodule 2023-10-10 10:09:36 +00:00
Capa Bot
248a51c15f Sync capa rules submodule 2023-10-10 09:55:31 +00:00
Aayush Goel
8a0628f357 Update CHANGELOG.md 2023-10-10 04:16:38 +05:30
Aayush Goel
2ec87f717a Merge branch 'mandiant:master' into Aayush-Goel-04/Issue#322 2023-10-10 04:06:28 +05:30
Capa Bot
4430fce314 Sync capa rules submodule 2023-10-09 18:13:48 +00:00
Capa Bot
174c8121ca Sync capa rules submodule 2023-10-09 18:01:23 +00:00
Capa Bot
fa1371cfa8 Sync capa rules submodule 2023-10-09 18:00:29 +00:00
Capa Bot
a0a2b07b85 Sync capa rules submodule 2023-10-09 16:35:56 +00:00
Moritz
a9daa92c9a Merge branch 'master' into Aayush-Goel-04/Issue#322 2023-10-09 18:22:46 +02:00
Capa Bot
b315aacd73 Sync capa rules submodule 2023-10-09 16:22:26 +00:00
Capa Bot
3dd051582a Sync capa rules submodule 2023-10-09 16:01:44 +00:00
Capa Bot
5f7b4fbf74 Sync capa rules submodule 2023-10-06 15:20:18 +00:00
Yacine Elhamer
8b287c1704 scripts/profile_time.py: revert restriction that sample extractors can only be static ones 2023-10-04 10:51:53 +02:00
Yacine Elhamer
28a722d4c3 scripts/profile_time.py: revert restriction that frozen extractors can only be static ones 2023-10-04 10:51:02 +02:00
Yacine Elhamer
35f64f37bb cape/global_.py: throw exceptions for unrecognized OSes, formats, and architectures 2023-10-04 10:36:08 +02:00
Yacine Elhamer
7d9ae57692 check for pid and ppid reuse 2023-10-04 10:28:10 +02:00
Mike Hunhoff
b1175ab16a adding capa quickstart reference (#1802) 2023-10-03 12:05:55 -06:00
dependabot[bot]
838205b375 build(deps-dev): bump dnfile from 0.13.0 to 0.14.1
Bumps [dnfile](https://github.com/malwarefrank/dnfile) from 0.13.0 to 0.14.1.
- [Changelog](https://github.com/malwarefrank/dnfile/blob/master/HISTORY.rst)
- [Commits](https://github.com/malwarefrank/dnfile/compare/v0.13.0...v0.14.1)

---
updated-dependencies:
- dependency-name: dnfile
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-02 14:42:47 +00:00
dependabot[bot]
0fbec49708 build(deps-dev): bump flake8-bugbear from 23.7.10 to 23.9.16
Bumps [flake8-bugbear](https://github.com/PyCQA/flake8-bugbear) from 23.7.10 to 23.9.16.
- [Release notes](https://github.com/PyCQA/flake8-bugbear/releases)
- [Commits](https://github.com/PyCQA/flake8-bugbear/compare/23.7.10...23.9.16)

---
updated-dependencies:
- dependency-name: flake8-bugbear
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-02 14:42:40 +00:00
dependabot[bot]
0bdc727dce build(deps-dev): bump black from 23.7.0 to 23.9.1
Bumps [black](https://github.com/psf/black) from 23.7.0 to 23.9.1.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/23.7.0...23.9.1)

---
updated-dependencies:
- dependency-name: black
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-02 14:42:28 +00:00
dependabot[bot]
8ea7708a38 build(deps-dev): bump wcwidth from 0.2.6 to 0.2.8
Bumps [wcwidth](https://github.com/jquast/wcwidth) from 0.2.6 to 0.2.8.
- [Release notes](https://github.com/jquast/wcwidth/releases)
- [Commits](https://github.com/jquast/wcwidth/compare/0.2.6...0.2.8)

---
updated-dependencies:
- dependency-name: wcwidth
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-02 14:42:16 +00:00
Capa Bot
9b5c906c2a Sync capa rules submodule 2023-09-27 20:40:53 +00:00
Willi Ballenthin
240376153a Merge pull request #1791 from xusheng6/test_binja_forwarded_export
binja: add support for forwarded exports
2023-09-27 11:35:00 +02:00
Willi Ballenthin
321ef100c5 Update capa/features/extractors/binja/helpers.py 2023-09-27 08:56:42 +02:00
Willi Ballenthin
d8eebf524e Update capa/features/extractors/binja/helpers.py 2023-09-27 08:51:12 +02:00
dependabot[bot]
c6c54c316f build(deps-dev): bump pyinstaller from 5.10.1 to 6.0.0
Bumps [pyinstaller](https://github.com/pyinstaller/pyinstaller) from 5.10.1 to 6.0.0.
- [Release notes](https://github.com/pyinstaller/pyinstaller/releases)
- [Changelog](https://github.com/pyinstaller/pyinstaller/blob/develop/doc/CHANGES.rst)
- [Commits](https://github.com/pyinstaller/pyinstaller/compare/v5.10.1...v6.0.0)

---
updated-dependencies:
- dependency-name: pyinstaller
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-27 06:50:58 +00:00
Willi Ballenthin
b1e00150f4 Merge pull request #1783 from mandiant/dependabot/pip/pytest-7.4.2
build(deps-dev): bump pytest from 7.4.0 to 7.4.2
2023-09-27 08:50:24 +02:00
Willi Ballenthin
83a7ce0b82 Merge pull request #1784 from mandiant/dependabot/pip/build-1.0.3
build(deps-dev): bump build from 0.10.0 to 1.0.3
2023-09-27 08:49:54 +02:00
Willi Ballenthin
303170f45d Merge pull request #1785 from mandiant/dependabot/pip/pyelftools-0.30
build(deps-dev): bump pyelftools from 0.29 to 0.30
2023-09-27 08:48:59 +02:00
Willi Ballenthin
8a019aa360 Merge branch 'master' into test_binja_forwarded_export 2023-09-27 08:48:21 +02:00
Willi Ballenthin
3dffa8145f Update capa/features/extractors/binja/helpers.py 2023-09-27 08:47:52 +02:00
Willi Ballenthin
782a5b3aa7 Merge pull request #1793 from mandiant/dependabot/pip/ruff-0.0.291
build(deps-dev): bump ruff from 0.0.290 to 0.0.291
2023-09-25 20:26:02 +02:00
dependabot[bot]
b0af78569c build(deps-dev): bump ruff from 0.0.290 to 0.0.291
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.0.290 to 0.0.291.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/BREAKING_CHANGES.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.0.290...v0.0.291)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-25 14:16:40 +00:00
Capa Bot
79cef0e783 Sync capa-testfiles submodule 2023-09-22 10:33:01 +00:00
Willi Ballenthin
09b54a86f0 Merge branch 'master' into test_binja_forwarded_export 2023-09-21 12:10:13 +02:00
Willi Ballenthin
57106701c4 Merge pull request #1792 from xusheng6/binja_symtab
binja: add support for symtab names. Fix #1504
2023-09-21 12:06:13 +02:00
Xusheng
55af6f052f binja: add support for symtab names. Fix #1504 2023-09-21 17:24:42 +08:00
Xusheng
d2d32f88ef binja: add support for forwarded exports 2023-09-21 15:32:55 +08:00
Willi Ballenthin
7abcf3de9a Merge pull request #1790 from xusheng6/test_update_bn_35 2023-09-21 07:13:51 +02:00
Xusheng
b3dccb3841 binja: improve function call site detection 2023-09-21 09:51:01 +08:00
Xusheng
bc71c94171 binja: use binaryninja.load to open a binary 2023-09-21 09:51:01 +08:00
Xusheng
59d03b3ba3 binja: bump Binary Ninja version to 3.5 2023-09-20 21:00:04 +08:00
Willi Ballenthin
3a5c8ec3b8 Merge pull request #1788 from mandiant/dependabot/pip/ruff-0.0.290
build(deps-dev): bump ruff from 0.0.286 to 0.0.290
2023-09-19 14:17:33 +02:00
dependabot[bot]
fd3678904a build(deps-dev): bump ruff from 0.0.286 to 0.0.290
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.0.286 to 0.0.290.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/BREAKING_CHANGES.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.0.286...v0.0.290)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-18 14:36:44 +00:00
Capa Bot
d04ae5294e Sync capa-testfiles submodule 2023-09-13 14:50:29 +00:00
Capa Bot
6bae9d757d Sync capa rules submodule 2023-09-13 14:46:47 +00:00
dependabot[bot]
b9c05cf44a build(deps-dev): bump pyelftools from 0.29 to 0.30
Bumps [pyelftools](https://github.com/eliben/pyelftools) from 0.29 to 0.30.
- [Changelog](https://github.com/eliben/pyelftools/blob/master/CHANGES)
- [Commits](https://github.com/eliben/pyelftools/compare/v0.29...v0.30)

---
updated-dependencies:
- dependency-name: pyelftools
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-11 14:14:27 +00:00
dependabot[bot]
dc32289aab build(deps-dev): bump build from 0.10.0 to 1.0.3
Bumps [build](https://github.com/pypa/build) from 0.10.0 to 1.0.3.
- [Release notes](https://github.com/pypa/build/releases)
- [Changelog](https://github.com/pypa/build/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pypa/build/compare/0.10.0...1.0.3)

---
updated-dependencies:
- dependency-name: build
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-11 14:14:20 +00:00
dependabot[bot]
3c1a8f4461 build(deps-dev): bump pytest from 7.4.0 to 7.4.2
Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.4.0 to 7.4.2.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/7.4.0...7.4.2)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-11 14:14:11 +00:00
Aayush Goel
8331ed6ea0 Merge branch 'mandiant:master' into Aayush-Goel-04/Issue#322 2023-09-06 16:35:29 +05:30
Mike Hunhoff
b0d55143a4 ghidra: update CI to use /Ghidra/Extensions (#1782) 2023-09-05 13:21:52 -06:00
Capa Bot
e006702245 Sync capa rules submodule 2023-09-05 13:02:13 +00:00
Willi Ballenthin
72e836166f proto: better convert to/from proto 2023-09-05 10:24:53 +00:00
Willi Ballenthin
d64ab41dfd tests: proto: add more dynamic proto tests 2023-09-05 10:23:55 +00:00
Willi Ballenthin
5b4c167489 proto: add additional types 2023-09-05 10:23:30 +00:00
Willi Ballenthin
2a757b0cbb submodule: test data: update 2023-09-05 10:22:59 +00:00
Willi Ballenthin
69836a0f13 proto: add dynamic test 2023-09-05 10:22:33 +00:00
Willi Ballenthin
866c7c5ce4 proto: deprecate metadata.analysis 2023-09-05 08:39:37 +00:00
Willi Ballenthin
3725618d50 render: proto: use Static/Dynamic analysis types 2023-09-05 08:37:11 +00:00
Willi Ballenthin
766b05e5c3 Merge branch 'dynamic-feature-extraction' into fix/dynamic-proto 2023-09-05 08:18:51 +00:00
Willi Ballenthin
1224b7e514 Merge pull request #1776 from mandiant/dependabot/pip/pre-commit-3.4.0
build(deps-dev): bump pre-commit from 3.3.3 to 3.4.0
2023-09-04 21:45:08 +02:00
dependabot[bot]
46e3ed1100 build(deps-dev): bump pre-commit from 3.3.3 to 3.4.0
Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 3.3.3 to 3.4.0.
- [Release notes](https://github.com/pre-commit/pre-commit/releases)
- [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md)
- [Commits](https://github.com/pre-commit/pre-commit/compare/v3.3.3...v3.4.0)

---
updated-dependencies:
- dependency-name: pre-commit
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-04 14:45:22 +00:00
Yacine Elhamer
dd0eadb438 freeze/__init__.py: bump freeze version to 3 2023-09-04 11:51:22 +02:00
Yacine Elhamer
f905ed611b Merge branch 'dynamic-feature-extraction' of https://github.com/mandiant/capa into dynamic-feature-extraction 2023-09-04 11:04:38 +02:00
Yacine Elhamer
cfa703eaae remove type comment 2023-09-04 11:04:09 +02:00
Yacine Elhamer
9ec1bf3e42 point rules towards dynamic-syntax 2023-09-04 10:38:01 +02:00
Yacine Elhamer
d83c0e70de main.py: remove comment type annotations 2023-09-04 09:59:29 +02:00
Yacine Elhamer
1d8e650d7b freeze/__init__.py: bump freeze version to 3 2023-09-04 09:50:29 +02:00
Yacine
99caa87a3d Update capa/main.py
Co-authored-by: Willi Ballenthin <wballenthin@google.com>
2023-09-04 09:46:41 +02:00
Mike Hunhoff
7b08f2d55a Merge pull request #1770 from mandiant/backend-ghidra
ghidra: add Ghidra feature extractor and supporting code
2023-08-30 10:41:01 -06:00
Mike Hunhoff
d17db614b9 Update README.md 2023-08-30 10:33:38 -06:00
Aayush Goel
6317153ef0 Update tests/test_rules.py
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2023-08-30 21:48:55 +05:30
Aayush Goel
24dad6bcc4 Update capa/rules/__init__.py
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2023-08-30 21:48:48 +05:30
Yacine Elhamer
73c158ad68 point submodules towards the right branch 2023-08-30 11:42:43 +02:00
Yacine Elhamer
47330e69d4 verbose.py render_dynamic_meta(): s/doc: rd.ResultDocument/meta: rd.MetaData/g 2023-08-29 22:42:18 +02:00
Yacine Elhamer
0987673bf3 verbose.py: temporarily add a mypy-related assert to render_static_meta() 2023-08-29 22:38:14 +02:00
Yacine Elhamer
2c75f786c3 main.py rdoc.Metadata creation: revert to usage of as_posix() within the call to rdoc.Sample() 2023-08-29 22:35:49 +02:00
Yacine Elhamer
09afcfbac1 render/verbose.py: remove frz.AddressType.FREEZE 2023-08-29 22:31:16 +02:00
Aayush Goel
ab3747e448 added com prefix CLSID, IID 2023-08-30 01:00:07 +05:30
colton-gabertan
72ed4d1165 push shellcode example 2023-08-29 18:05:03 +00:00
colton-gabertan
0ec682a464 add shellcode documentation & update Headless Analyzer example 2023-08-29 18:01:11 +00:00
colton-gabertan
37917b6181 update ghidra feat extractor docs 2023-08-29 17:28:49 +00:00
Mike Hunhoff
a6e61ed6f1 Update capa/ghidra/README.md
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2023-08-29 09:03:26 -06:00
Mike Hunhoff
1fddf800c6 Update capa/ghidra/README.md
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2023-08-29 09:02:46 -06:00
Mike Hunhoff
0ffd631606 Update .github/workflows/tests.yml
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2023-08-29 09:00:14 -06:00
Mike Hunhoff
7cc10401d5 fix #1772 2023-08-28 15:15:47 -06:00
Mike Hunhoff
3929164fc2 Merge branch 'backend-ghidra' of github.com:mandiant/capa into backend-ghidra 2023-08-28 13:24:23 -06:00
Mike Hunhoff
f3a2a5958d fix Ghidra detection 2023-08-28 13:24:14 -06:00
Colton Gabertan
6d3f649a0c remove backend-ghidra from CI 2023-08-28 12:21:30 -07:00
Colton Gabertan
e00608e298 ghidra hotfix: fix ghidrathon download (#1771)
* hotfix: fix ghidrathon download
2023-08-28 12:19:45 -07:00
Mike Hunhoff
995014afc2 merge upstream 2023-08-28 12:40:49 -06:00
Mike Hunhoff
a522ae20f1 update CHANGELOG 2023-08-28 12:40:02 -06:00
Mike Hunhoff
203fc36865 cleanup CHANGELOG merge 2023-08-28 12:33:07 -06:00
Mike Hunhoff
7bd2467074 remove backend-ghidra from workflows 2023-08-28 12:32:52 -06:00
Willi Ballenthin
f339bbf68c Merge pull request #1769 from mandiant/dependabot/pip/ruff-0.0.286
build(deps-dev): bump ruff from 0.0.285 to 0.0.286
2023-08-28 20:26:11 +02:00
Mike Hunhoff
8ed4062cf1 sync rules submodule with upstream 2023-08-28 12:13:10 -06:00
dependabot[bot]
807792f879 build(deps-dev): bump ruff from 0.0.285 to 0.0.286
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.0.285 to 0.0.286.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/BREAKING_CHANGES.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.0.285...v0.0.286)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-28 14:48:55 +00:00
Yacine
9dc457e61e Update capa/features/freeze/__init__.py
Co-authored-by: Willi Ballenthin <wballenthin@google.com>
2023-08-28 15:40:31 +02:00
Yacine Elhamer
9eb88e6ca7 Merge branch 'dynamic-feature-extraction' of https://github.com/mandiant/capa into dynamic-feature-extraction 2023-08-28 13:24:58 +02:00
Yacine Elhamer
214a355b9c binja extractor: remove unused pathlib.Path import 2023-08-28 13:24:54 +02:00
Colton Gabertan
9cea7346b2 ghidra: documentation (#1759)
* Implement ghidra documentation
2023-08-27 19:21:36 -07:00
Yacine
4d538b939e Update scripts/import-to-ida.py
Co-authored-by: Willi Ballenthin <wballenthin@google.com>
2023-08-27 14:59:10 +02:00
Yacine Elhamer
8c9e676868 binja: use binja api's methods to get the file hash 2023-08-27 14:31:43 +02:00
Yacine Elhamer
b0133f0aa1 various fixes 2023-08-26 19:28:07 +02:00
Yacine Elhamer
49adecb25c add yaml representer for the Scope class, as well as other bugfixes 2023-08-26 18:11:35 +02:00
Yacine Elhamer
e9a9b3a6b6 point the data file to the latest PR 2023-08-26 13:04:45 +02:00
colton-gabertan
d7c9ae26bc Merge branch 'master' into backend-ghidra 2023-08-26 02:08:22 +00:00
Colton Gabertan
fddec33d04 ghidra: fix api info caching (#1766)
* cache and retrive imports, externs, and fakes in FunctionHandle objects

* reduce cache retreival calls

* cache in GhidraFeatureExtractor, point fh.ctx to cache

* move caching routine to __init__
2023-08-25 19:03:38 -07:00
Mike Hunhoff
65179805a7 add a Ghidra entry script users can invoke to run capa against a loaded Ghidra database (#1767)
* enable use of Ghidra with show-features.py

* fix bug in is_supported_file_type

* fix bug in GhidraFeatureExtractor.get_function

* refactor get_insn_in_range

* add Ghidra entry script for users to more easily run capa against a loaded Ghidra database

* update CHANGELOG

* fixing lint

* fix fixtures import issue

* fix bug in is_supported_arch_type

* add check for supported arch type

* fix extract_embedded_pe performance
2023-08-25 18:35:59 -07:00
Yacine
d5daa79547 Merge pull request #1764 from mandiant/fix/scope-enum-usage
rules: use Scope enum instead of constants
2023-08-25 20:58:34 +03:00
Aayush Goel
90df85b332 test for com_feature
matching a file as expected
generating the bytes/strings
if an unknown COM class/interface is provided?
2023-08-25 20:59:58 +05:30
Willi Ballenthin
88ee6e661e wip: proto: add Metadata.[static, dynamic]_analysis 2023-08-25 14:40:50 +00:00
Willi Ballenthin
08c9bbcc91 proto: deprecate RuleMetadata.scope 2023-08-25 13:22:48 +00:00
Willi Ballenthin
f96b9e6a6e proto: add RuleMetadata.scopes 2023-08-25 13:20:46 +00:00
Willi Ballenthin
9bbd3184b0 rules: handle unsupported scopes again 2023-08-25 13:15:55 +00:00
Willi Ballenthin
e4c1361d42 Merge branch 'fix/scope-enum-usage' into fix/dynamic-proto 2023-08-25 13:01:49 +00:00
Willi Ballenthin
17e4765728 changelog 2023-08-25 13:00:34 +00:00
Willi Ballenthin
7e258a91ec Merge branch 'dynamic-feature-extraction' into fix/scope-enum-usage 2023-08-25 14:59:18 +02:00
Willi Ballenthin
b88853f327 changelog 2023-08-25 14:59:03 +02:00
Willi Ballenthin
a60401fc7e Merge branch 'master' into dynamic-feature-extraction 2023-08-25 14:58:35 +02:00
Willi Ballenthin
a734358377 rules: use Scope enum instead of constants 2023-08-25 12:54:57 +00:00
Willi Ballenthin
ebcbad3ae3 proto: add new scopes 2023-08-25 12:21:37 +00:00
Willi Ballenthin
8ff74d4a04 proto: regenerate using 3.21 protoc 2023-08-25 12:20:51 +00:00
Aayush Goel
bd0d8eb403 Update __init__.py
added parse_description for com feature
Update CHANGELOG.md
added comments, dealt with errors
2023-08-25 16:04:25 +05:30
Aayush Goel
9b79aa1983 Merge branch 'Aayush-Goel-04/Issue#322' of https://github.com/Aayush-Goel-04/capa into Aayush-Goel-04/Issue#322 2023-08-25 15:42:17 +05:30
Aayush Goel
172968c77e Update CHANGELOG.md 2023-08-25 15:42:02 +05:30
Aayush Goel
f1a7049ab5 Merge branch 'master' into Aayush-Goel-04/Issue#322 2023-08-25 15:39:03 +05:30
Aayush Goel
155a2904fb Update CHANGELOG.md 2023-08-25 15:38:00 +05:30
Aayush Goel
4c2e8fd718 Merge branch 'Aayush-Goel-04/Issue#322' of https://github.com/Aayush-Goel-04/capa into Aayush-Goel-04/Issue#322 2023-08-25 15:33:52 +05:30
Aayush Goel
95e279a03b update com db
moved code to rules/init.py , create db for coms
2023-08-25 15:32:40 +05:30
Willi Ballenthin
f2909c82f3 proto: reenable tests and linters 2023-08-25 09:41:25 +00:00
Willi Ballenthin
164b08276c extractor: tweak hashes to fix mypy 2023-08-25 09:38:23 +00:00
Willi Ballenthin
b930523d44 freeze: add TODO issue link 2023-08-25 11:32:56 +02:00
Willi Ballenthin
9d21addc6b Merge pull request #1763 from mandiant/v6.1.0
version: v6.1.0
2023-08-25 11:11:59 +02:00
Willi Ballenthin
9accb60eff changelog 2023-08-25 09:11:04 +00:00
Willi Ballenthin
61202913a6 changelog 2023-08-25 09:07:09 +00:00
Willi Ballenthin
2b59fef1b2 changelog 2023-08-25 09:05:57 +00:00
Willi Ballenthin
ddff8634de changelog 2023-08-25 09:04:26 +00:00
Willi Ballenthin
1905f1bfbd changelog 2023-08-25 09:02:03 +00:00
Yacine Elhamer
f34b0355e7 test_result_document.py: re-enable result-document related tests 2023-08-25 10:56:12 +02:00
Willi Ballenthin
7a70bc9b2a version: v6.1.0 2023-08-25 08:47:11 +00:00
Yacine
3ee56e3bee Merge pull request #1762 from yelhamer/modify-sample-hashes
Modify sample hashes
2023-08-25 10:29:38 +03:00
Yacine Elhamer
49bf2eb6d4 base_extractor.py: replace dunder with single underscore for sample_hashes attribute 2023-08-25 10:14:25 +02:00
Yacine Elhamer
707dee4c3f base_Extractor.py: make sample_hashes attribute private 2023-08-25 09:53:08 +02:00
Yacine Elhamer
0ded827290 modify null extractor 2023-08-25 08:50:34 +02:00
Yacine Elhamer
f74107d960 initial commit 2023-08-25 08:37:57 +02:00
Mike Hunhoff
448b122ef0 fix ints_to_bytes performance (#1761)
* fix ints_to_bytes performance
2023-08-24 16:01:41 -07:00
colton-gabertan
bd2f7bc1f4 hotfix: fix indirect address dereference handling 2023-08-24 22:09:08 +00:00
Yacine
acd3a30d27 Merge pull request #1758 from yelhamer/fix-cape2fmt
Add dynamic scopes to capa2fmt
2023-08-24 15:43:34 +03:00
Yacine Elhamer
b636f23e3c Merge branch 'fix-cape2fmt' of https://github.com/yelhamer/capa into fix-cape2fmt 2023-08-24 15:01:00 +02:00
Yacine Elhamer
70eae1a6f0 freeze/__init__.py: fix missing space 2023-08-24 15:00:34 +02:00
Yacine Elhamer
3574bd49bd Merge remote-tracking branch 'parentrepo/dynamic-feature-extraction' into fix-cape2fmt 2023-08-24 14:48:07 +02:00
Yacine Elhamer
46217a3acb test_main.py: remove unused pytest 2023-08-24 14:47:40 +02:00
Yacine Elhamer
9eb1255b29 cape2yara.py: update for use of scopes, and fix bug 2023-08-24 14:32:49 +02:00
Yacine
d66f834e54 Update tests/test_scripts.py
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2023-08-24 13:48:32 +02:00
Yacine Elhamer
7c101f01e5 test_binja.py: revert ruleset-related xfails 2023-08-24 13:36:53 +02:00
Yacine Elhamer
42689ef1da test_main.py: revert ruleset-related xfails 2023-08-24 13:30:22 +02:00
Colton Gabertan
70d36ab640 properly set bounds for find_byte_sequence (#1757) 2023-08-23 15:40:15 -06:00
Colton Gabertan
19b8000c00 Ghidra: Fixes & Enhancements (#1733)
* restore from corrupted .git

* lint repo

* temp: remove lint failing rule

* implement dereferencing, clean up extractors

* implement proper dereferencing routines as applicable

* fix nzxor implementation, remediate ghidra analysis issues

* lint repo

* Assert typing, lint repo

* avoid extracting pointers in bytes extraction

* attempt to recover submodule

* implement GhidraFeatureExtractor & ghidra_main()

* lint repo

* document examples, clean-up & testing

* lint repo

* properly map import dict

* properly map fake addresses

* fix fake addr mapping

* properly map externs

* re-align consistency with other backends

* lint repo

* fix dereferencing routine

* clean up helpers

* fix format string

* disable progress bar to exit gracefully

* enable pbar in headless runtime mode

* implement fixture test script

* implement ghidra unit test script

* refactor repo for breaking Ghidrathon change

* bump ghidrathon CI version, run unit test in CI

* change CI config

* fix wget line for ghidrathon

* fix unzip paths

* fix ghidra import issue

* disable pytest faulthandler module

* fix dereference function

* fix ghidra state variables

* implement dereferencing for string extraction

* use toAddr

* restructure for consistency

* Bump Ghidrathon version for CI, fix pytest ghidra runtime detection

* fix number & offset extractors

* yield both signed & unsgned values for offset extraction

* add LEA insn handling to number & offset extraction

* fix indirect call extraction

* implement thunk function checking for dereferences

* revise ghidra feature count tests, pass unit testing

* fix feature test format

* implement additional support for dereferencing thunked functions

* integrate external locations into find_file_imports

* change api yield string for .elf samples to match other extractors

* fix potential NoneType errors during dereferencing

* user helper in global_

* fix GHIDRAIO class, implement in global_

* comment on getOriginalByte

* simplify get_file_imports

* implement explicit thunk chain handling

* simplify LEA number extraction

* simplify thunk handling

* temp: demonstrate CI failure & output

* fix log path

* run new test against mimikatz
2023-08-23 14:35:18 -06:00
colton-gabertan
06f48063d0 Merge branch 'master' into backend-ghidra 2023-08-23 18:05:58 +00:00
Yacine
5ba7325646 Merge pull request #1753 from yelhamer/update-linter
Update the rules linter
2023-08-23 11:50:51 +03:00
Yacine
86effec1a2 capa/rules/__init__.py: merge features from small scopes into larger ones
Co-authored-by: Willi Ballenthin <wballenthin@google.com>
2023-08-23 08:49:36 +03:00
Yacine
cdb469eca0 capa/features/freeze/__init__.py: remove comment
Co-authored-by: Willi Ballenthin <wballenthin@google.com>
2023-08-23 08:45:21 +03:00
Yacine
39c8fd8286 Update capa/features/freeze/__init__.py
Co-authored-by: Willi Ballenthin <wballenthin@google.com>
2023-08-23 08:43:36 +03:00
Yacine Elhamer
5730e5515f lint.py: update recommendation messages 2023-08-23 01:42:22 +02:00
Yacine Elhamer
901ba551bc lint.py: fix boolean statement 2023-08-23 01:41:44 +02:00
Yacine Elhamer
77b3fadf79 lint.py: add 'unsupported' keyword 2023-08-23 01:39:14 +02:00
Yacine Elhamer
44fc3357d1 initial commit 2023-08-23 01:32:01 +02:00
Willi Ballenthin
25414044ef Merge pull request #1748 from mandiant/feat/issue-1744
rules: add scope terms "unsupported" and "unspecified"
2023-08-22 15:59:57 +02:00
Yacine Elhamer
d1068991e3 test_rules_insn_scope.py: update rules missing the dynamic scope 2023-08-22 16:26:54 +02:00
Willi Ballenthin
4ab240e990 rules: add scope terms "unsupported" and "unspecified"
closes #1744
2023-08-22 12:58:06 +00:00
Willi Ballenthin
9489927bed Merge pull request #1746 from mandiant/fix/issue-1745
fix detection of CAPE reports
2023-08-22 14:34:23 +02:00
Willi Ballenthin
c160f45849 main: fix rendering of logging message 2023-08-22 12:32:53 +00:00
Willi Ballenthin
5b585c0e39 cape: better detect CAPE reports
fixes #1745
2023-08-22 12:32:30 +00:00
Aayush Goel
c6ee919619 Update capa/features/common.py
Co-authored-by: Willi Ballenthin <wballenthin@google.com>
2023-08-22 15:52:04 +05:30
Willi Ballenthin
675ad364ac point submodule rules to branch dynamic-syntax 2023-08-22 08:50:18 +00:00
Willi Ballenthin
21cefa0932 Merge branch 'master' into dynamic-feature-extraction 2023-08-22 09:53:42 +02:00
Willi Ballenthin
934d0f969b Merge pull request #1740 from mandiant/dependabot/pip/mypy-1.5.1
build(deps-dev): bump mypy from 1.5.0 to 1.5.1
2023-08-22 09:53:15 +02:00
dependabot[bot]
b7b79b565b build(deps-dev): bump mypy from 1.5.0 to 1.5.1
Bumps [mypy](https://github.com/python/mypy) from 1.5.0 to 1.5.1.
- [Commits](https://github.com/python/mypy/compare/v1.5.0...v1.5.1)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-22 07:48:33 +00:00
Moritz
979aab3098 Merge pull request #1741 from mandiant/dependabot/pip/ruff-0.0.285
build(deps-dev): bump ruff from 0.0.284 to 0.0.285
2023-08-22 09:47:50 +02:00
Willi Ballenthin
89c8c6d212 Update capa/rules/__init__.py 2023-08-22 09:38:41 +02:00
Willi Ballenthin
e5af7165ea Update capa/features/freeze/__init__.py 2023-08-22 09:31:35 +02:00
Willi Ballenthin
ee936f9257 Merge pull request #1729 from mandiant/feat/cape-pydantic
add Pydantic models for CAPE sandbox
2023-08-22 09:25:02 +02:00
Colton Gabertan
058c1fefd2 ghidra: unit tests (#1727)
* restore from corrupted .git

* lint repo

* temp: remove lint failing rule

* implement dereferencing, clean up extractors

* implement proper dereferencing routines as applicable

* fix nzxor implementation, remediate ghidra analysis issues

* lint repo

* Assert typing, lint repo

* avoid extracting pointers in bytes extraction

* attempt to recover submodule

* implement GhidraFeatureExtractor & ghidra_main()

* lint repo

* document examples, clean-up & testing

* lint repo

* properly map import dict

* properly map fake addresses

* fix fake addr mapping

* properly map externs

* re-align consistency with other backends

* lint repo

* fix dereferencing routine

* clean up helpers

* fix format string

* disable progress bar to exit gracefully

* enable pbar in headless runtime mode

* implement fixture test script

* implement ghidra unit test script

* refactor repo for breaking Ghidrathon change

* bump ghidrathon CI version, run unit test in CI

* change CI config

* fix wget line for ghidrathon

* fix unzip paths

* fix ghidra import issue

* disable pytest faulthandler module

* fix ghidra state variables

* use toAddr

* restructure for consistency

* Bump Ghidrathon version for CI, fix pytest ghidra runtime detection
2023-08-21 12:16:13 -06:00
dependabot[bot]
8ed00a2847 build(deps-dev): bump ruff from 0.0.284 to 0.0.285
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.0.284 to 0.0.285.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/BREAKING_CHANGES.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.0.284...v0.0.285)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-21 14:50:50 +00:00
Aayush Goel
6482848fa4 Merge branch 'Aayush-Goel-04/Issue#322' of https://github.com/Aayush-Goel-04/capa into Aayush-Goel-04/Issue#322 2023-08-20 00:39:50 +05:30
Aayush Goel
7c2a736c4b Update CHANGELOG.md 2023-08-20 00:38:35 +05:30
Aayush Goel
918ec22667 Merge branch 'master' into Aayush-Goel-04/Issue#322 2023-08-20 00:38:26 +05:30
Aayush Goel
1027da9be0 add new feature for com 2023-08-20 00:36:37 +05:30
Capa Bot
5787e41dd2 Sync capa rules submodule 2023-08-19 18:14:15 +00:00
Capa Bot
0265657937 Sync capa rules submodule 2023-08-19 09:36:35 +00:00
Capa Bot
73477b6495 Sync capa rules submodule 2023-08-19 09:34:30 +00:00
Yacine Elhamer
521bd25d31 remove file-limitations checks for dynamic extractors 2023-08-18 15:23:19 +02:00
Yacine Elhamer
e7c0bea6e5 Match.from_capa(): remove reliance on the meta field to get the scope 2023-08-18 15:05:15 +02:00
Yacine Elhamer
a8bd5b1119 disable packed-sample warning for dynamic feature extractors 2023-08-18 14:31:32 +02:00
Yacine Elhamer
9144d12e51 add error message for invalid report files 2023-08-18 14:28:02 +02:00
Yacine Elhamer
d741544514 result_document.py: use the scopes attribute instead of meta["scope"] 2023-08-18 14:15:36 +02:00
Willi Ballenthin
5e31f0df23 cape: models: more fixes thanks to avast 2023-08-18 10:19:07 +00:00
Willi Ballenthin
18dff9d664 cape: models: more fixes thanks to avast 2023-08-18 10:15:12 +00:00
Yacine Elhamer
350094759a main.py: look up rules scope with scopes attribute, not their meta field 2023-08-18 12:37:42 +02:00
Willi Ballenthin
b10275e851 black 2023-08-18 08:23:21 +00:00
Willi Ballenthin
05cf7201ad Merge branch 'dynamic-feature-extraction' into feat/cape-pydantic 2023-08-18 10:22:55 +02:00
Willi Ballenthin
8cd5e03e87 ci: pre-commit: show-diff-on-failure 2023-08-18 08:19:27 +00:00
Willi Ballenthin
120917e0b5 cape: models: tweaks from Avast dataset 2023-08-18 08:10:55 +00:00
colton-gabertan
a2a2949675 Merge branch 'master' into backend-ghidra 2023-08-17 16:06:17 +00:00
Colton Gabertan
b3cf1129e3 Ghidra: Implement GhidraFeatureExtractor (#1681)
* Implement GhidraFeatureExtractor & repo changes
2023-08-16 15:58:47 -07:00
Yacine
264958ebfe Update capa/features/common.py
Co-authored-by: Willi Ballenthin <wballenthin@google.com>
2023-08-16 16:12:26 +02:00
Willi Ballenthin
3614ce1409 cape: fix test failures 2023-08-16 11:43:45 +00:00
Willi Ballenthin
c80542ded3 cape: call: fix argument type switch 2023-08-16 11:37:41 +00:00
Willi Ballenthin
3350a936b7 ida: use ida_nalt not idaapi
closes #1730
2023-08-16 13:33:01 +02:00
Willi Ballenthin
724db83920 cape: require PE analysis 2023-08-16 13:23:00 +02:00
Willi Ballenthin
8788a40d12 Merge branch 'dynamic-feature-extraction' into feat/cape-pydantic 2023-08-16 13:13:29 +02:00
Willi Ballenthin
6f7bf96776 cape: use pydantic model 2023-08-16 11:12:05 +00:00
Willi Ballenthin
e943a71dff cape: models: relax deserializing FlexibleModels 2023-08-16 10:04:20 +00:00
Willi Ballenthin
4be1c89c5b cape: models: more data shapes 2023-08-16 09:50:13 +00:00
Willi Ballenthin
2eda053c79 cape: models: more data shapes 2023-08-16 09:41:36 +00:00
Willi Ballenthin
26539e68d9 cape: models: add tests 2023-08-16 08:57:54 +00:00
Willi Ballenthin
046427cf55 cape: model: document the data we'll use in cape 2023-08-16 08:57:17 +00:00
Willi Ballenthin
25aabcd7e4 cape: models: more shapes 2023-08-16 07:48:59 +00:00
Willi Ballenthin
d8bea816dd cape: models: add more fields 2023-08-15 14:36:49 +00:00
Willi Ballenthin
bb2b1824a9 Merge branch 'master' into dynamic-feature-extraction 2023-08-15 14:01:30 +02:00
Willi Ballenthin
7e78133925 Merge pull request #1728 from mandiant/fix/issue-1719
fix deprecation warnings
2023-08-15 14:00:15 +02:00
Willi Ballenthin
59a129d6d6 cape: add pydantic model for v2.2 2023-08-15 11:54:15 +00:00
Willi Ballenthin
db40d9bc7a wip: add initial CAPE model 2023-08-15 11:41:11 +00:00
Yacine
d71ecc7a79 Update tests/test_ida_features.py
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2023-08-15 12:26:19 +02:00
Yacine
a5a1a0bfee Update CHANGELOG.md
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2023-08-15 12:26:02 +02:00
Willi Ballenthin
827b4b29b4 test_rules: fix rule scoping logic 2023-08-15 09:21:49 +00:00
Willi Ballenthin
2a31b16567 merge 2023-08-15 08:56:41 +00:00
Willi Ballenthin
8118a3f353 changelog 2023-08-15 08:46:18 +00:00
Willi Ballenthin
e6d64ef561 pydantic: remove use of deprecated routines
closes #1718
2023-08-15 08:41:56 +00:00
Willi Ballenthin
408c5076c6 tests: ida: don't collect tests as pytest tests
closes #1719
2023-08-15 08:26:59 +00:00
Willi Ballenthin
c001c883f7 Merge pull request #1714 from mandiant/fix/issue-1697-1
rule scoping tweaks
2023-08-15 10:16:01 +02:00
Willi Ballenthin
476c7ff749 main: provide encoding to open
fixes flake8 warning
2023-08-15 08:13:22 +00:00
Willi Ballenthin
4978aa74e7 tests: temporarily xfail script test
closes #1717
2023-08-15 08:13:14 +00:00
Yacine Elhamer
4411911664 Merge remote-tracking branch 'parentrepo/dynamic-feature-extraction' into fix/issue-1697-1 2023-08-15 09:57:13 +02:00
Yacine
0e1ce21488 Merge pull request #1715 from mandiant/fix/issue-1710
fix rendering of scope in vverbose mode
2023-08-15 09:51:53 +02:00
Yacine
88aa17fa7b Merge pull request #1716 from mandiant/fix/issue-1697-2
remove dynamic return address concept
2023-08-15 08:55:12 +02:00
Willi Ballenthin
3169ee28e9 Merge pull request #1721 from mandiant/fix/issue-1704
elf: fix parsing of symtab from viv data
2023-08-14 17:13:50 +02:00
Willi Ballenthin
d648fdf6c0 Merge pull request #1713 from mandiant/fix/issue-1711
record and show the analysis flavor
2023-08-14 16:44:42 +02:00
Willi Ballenthin
3b9f5114ce Merge pull request #1722 from mandiant/dependabot/pip/mypy-1.5.0
build(deps-dev): bump mypy from 1.4.1 to 1.5.0
2023-08-14 16:43:57 +02:00
dependabot[bot]
623fc270c1 build(deps-dev): bump mypy from 1.4.1 to 1.5.0
Bumps [mypy](https://github.com/python/mypy) from 1.4.1 to 1.5.0.
- [Commits](https://github.com/python/mypy/compare/v1.4.1...v1.5.0)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-14 14:43:40 +00:00
Willi Ballenthin
1199fb94d4 Merge pull request #1723 from mandiant/dependabot/pip/tqdm-4.66.1
build(deps-dev): bump tqdm from 4.65.0 to 4.66.1
2023-08-14 16:43:18 +02:00
Willi Ballenthin
26fdbbd442 Merge pull request #1725 from mandiant/dependabot/pip/ruff-0.0.284
build(deps-dev): bump ruff from 0.0.282 to 0.0.284
2023-08-14 16:42:26 +02:00
Willi Ballenthin
737fab7969 elf: use equality not bit masking 2023-08-14 16:40:45 +02:00
dependabot[bot]
f6ee465a0a build(deps-dev): bump ruff from 0.0.282 to 0.0.284
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.0.282 to 0.0.284.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/BREAKING_CHANGES.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.0.282...v0.0.284)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-14 14:16:45 +00:00
dependabot[bot]
82f352f719 build(deps-dev): bump tqdm from 4.65.0 to 4.66.1
Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.65.0 to 4.66.1.
- [Release notes](https://github.com/tqdm/tqdm/releases)
- [Commits](https://github.com/tqdm/tqdm/compare/v4.65.0...v4.66.1)

---
updated-dependencies:
- dependency-name: tqdm
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-14 14:16:16 +00:00
Yacine Elhamer
846bd62817 Merge remote-tracking branch 'parentrepo/dynamic-feature-extraction' into fix/issue-1711 2023-08-14 16:05:20 +02:00
Yacine
84cddc70fd Merge pull request #1709 from mandiant/fix/issue-1702
fix rendering of call and return addresses
2023-08-14 16:00:16 +03:00
Willi Ballenthin
2dc5295c0c Merge branch 'master' into fix/issue-1704 2023-08-14 13:15:23 +02:00
Willi Ballenthin
8479bc2f1f Merge pull request #1720 from mandiant/fix/issue-1705
elf: detect Android OS via note and dependencies
2023-08-14 13:11:23 +02:00
Capa Bot
7c1522d84d Sync capa-testfiles submodule 2023-08-14 11:11:05 +00:00
Willi Ballenthin
9afe19a096 changelog 2023-08-14 11:10:06 +00:00
Willi Ballenthin
bd5c65d22c elf: fix parsing of symtab from viv
closes #1704
2023-08-14 11:08:19 +00:00
Willi Ballenthin
e6cb3d3b3b os: detect Android via dependencies, too 2023-08-14 10:27:19 +00:00
Willi Ballenthin
18058beb0a changelog 2023-08-14 10:20:30 +00:00
Willi Ballenthin
8003547414 elf: detect Android OS via note
closes #1705
2023-08-14 10:13:42 +00:00
Yacine
2a83f1fc23 Merge pull request #1708 from mandiant/fix/issue-1707
tests: create workspaces only during tests, not import
2023-08-14 12:24:02 +03:00
Yacine Elhamer
751231b730 fixtures.py: fix the path of '0000a567' in get_data_path_by_name() method 2023-08-14 12:37:15 +03:00
Willi Ballenthin
c6d400bcf3 address: remove dynamic return address concept, as its unused today 2023-08-11 11:18:54 +00:00
Willi Ballenthin
fd1cd05b99 vverbose: render relevant scope at top of match tree
closes #1710
2023-08-11 10:59:44 +00:00
Willi Ballenthin
8202e9e921 main: don't use analysis flavor to filter rules
im worried this will interact poorly with our rule cache,
unless we add more handling there, which needs more testing.
so, since the filtering likely has only a small impact on performance,
revert the rule filtering changes for simplicity.
2023-08-11 10:36:59 +00:00
Willi Ballenthin
3c069a6784 rules: don't change passed-in argument
make a local copy of the scopes dict
2023-08-11 10:35:40 +00:00
Willi Ballenthin
e100a63cc8 rules: use set instead of tuple, add doc
since the primary operation is `contain()`,
set is more appropriate than tuple.
2023-08-11 10:34:41 +00:00
Willi Ballenthin
3057b5fb9d render: show analysis flavor
closes #1711
2023-08-11 09:49:13 +00:00
Willi Ballenthin
c91dc71e75 result document: wire analysis flavor through metadata
ref #1711
2023-08-11 09:33:30 +00:00
Willi Ballenthin
f48e4a8ad8 render: verbose: render dynamic call return address 2023-08-11 09:07:11 +00:00
Willi Ballenthin
dafbefb325 render: verbose: render call address
closes #1702
2023-08-11 09:02:29 +00:00
Willi Ballenthin
6de23a9748 tests: main: demonstrate CAPE analysis (and bug #1702) 2023-08-11 08:56:06 +00:00
Willi Ballenthin
1cf33e4343 tests: create workspaces only during tests, not import
closes #1707
2023-08-11 08:38:06 +00:00
Willi Ballenthin
34db63171f sync submodule testfiles 2023-08-11 08:36:29 +00:00
Capa Bot
ec93ca5b21 Sync capa rules submodule 2023-08-11 07:07:57 +00:00
colton-gabertan
2de6dc7cb8 Merge branch 'master' into backend-ghidra 2023-08-10 12:14:44 -07:00
Willi Ballenthin
19495f69d7 freeze: pydantic v2 fixes 2023-08-10 13:29:52 +00:00
Willi Ballenthin
c1fbb27d73 Merge branch 'master' into dynamic-feature-extraction 2023-08-10 13:21:49 +00:00
Willi Ballenthin
3cf748a135 vverbose: render both scopes nicely 2023-08-10 11:39:56 +02:00
Willi Ballenthin
85b58d041b process: simplify string enumeration loop 2023-08-10 11:38:43 +02:00
Willi Ballenthin
ae9d773e04 add TODO for typing.TypeAlias 2023-08-10 11:37:50 +02:00
Willi Ballenthin
582bb7c897 docstrings: improve wording 2023-08-10 11:36:51 +02:00
Capa Bot
e5efc158b7 Sync capa-testfiles submodule 2023-08-10 07:26:08 +00:00
Willi Ballenthin
9f436763f7 Merge pull request #1683 from Aayush-Goel-04/Aayush-Goel-04/Issue#331 2023-08-09 12:44:48 +02:00
Aayush Goel
a383022cff Update show-unused-features.py 2023-08-09 15:37:38 +05:30
Aayush Goel
57486733e7 Update scripts/show-unused-features.py
Co-authored-by: Willi Ballenthin <wballenthin@google.com>
2023-08-09 15:33:03 +05:30
Aayush Goel
df9828dd7f Update capa/rules/__init__.py
Co-authored-by: Willi Ballenthin <wballenthin@google.com>
2023-08-09 15:32:12 +05:30
Aayush Goel
d81f3a461e Update show-unused-features.py 2023-08-09 14:20:53 +05:30
Willi Ballenthin
f1e737ac92 Merge branch 'master' into Aayush-Goel-04/Issue#331 2023-08-09 08:53:02 +02:00
Mike Hunhoff
448aa9cd21 explorer: fix unhandled exception when resolving rule path (#1693) 2023-08-08 14:04:46 -06:00
colton-gabertan
f2c0509f81 Merge branch 'master' into backend-ghidra 2023-08-08 11:00:10 -07:00
Colton Gabertan
6287fbb958 Ghidra insn features (#1670)
* Implement Ghidra Instruction Feature Extraction
2023-08-08 10:45:14 -07:00
Aayush Goel
c497ad8253 Update show-unused-features.py 2023-08-08 18:36:25 +05:30
Aayush Goel
9c1aa2fc5d Update show-unused-features.py 2023-08-08 18:35:04 +05:30
Aayush Goel
f5a254f21f Merge branch 'master' into Aayush-Goel-04/Issue#331 2023-08-08 17:05:03 +05:30
Aayush Goel
fb3ae0267e Update scripts/show-unused-features.py
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2023-08-08 17:04:23 +05:30
Willi Ballenthin
5400576d4e Merge pull request #1689 from mandiant/dependabot/pip/ruff-0.0.282
build(deps-dev): bump ruff from 0.0.280 to 0.0.282
2023-08-08 11:02:54 +02:00
Willi Ballenthin
dabd9d0810 Merge pull request #1690 from mandiant/dependabot/pip/flake8-6.1.0
build(deps-dev): bump flake8 from 6.0.0 to 6.1.0
2023-08-08 11:02:24 +02:00
Willi Ballenthin
2bd777dbe4 Merge pull request #1691 from mandiant/dependabot/pip/types-protobuf-4.23.0.3
build(deps-dev): bump types-protobuf from 4.23.0.2 to 4.23.0.3
2023-08-08 11:02:12 +02:00
Willi Ballenthin
959c64b484 Merge pull request #1692 from mandiant/dependabot/pip/mypy-protobuf-3.5.0
build(deps-dev): bump mypy-protobuf from 3.4.0 to 3.5.0
2023-08-08 11:01:58 +02:00
Aayush Goel
232c9ce35c Add test for script & output rendered 2023-08-07 22:43:25 +05:30
Aayush Goel
b3a9763a32 Merge branch 'master' into Aayush-Goel-04/Issue#331 2023-08-07 21:02:42 +05:30
Aayush Goel
0fdc1dd3f5 Type Hints done , get_all_feature to Rule class 2023-08-07 21:00:29 +05:30
dependabot[bot]
80e224ec7c build(deps-dev): bump mypy-protobuf from 3.4.0 to 3.5.0
Bumps [mypy-protobuf](https://github.com/nipunn1313/mypy-protobuf) from 3.4.0 to 3.5.0.
- [Changelog](https://github.com/nipunn1313/mypy-protobuf/blob/main/CHANGELOG.md)
- [Commits](https://github.com/nipunn1313/mypy-protobuf/compare/v3.4.0...v3.5.0)

---
updated-dependencies:
- dependency-name: mypy-protobuf
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-07 14:42:44 +00:00
dependabot[bot]
75a4f309b4 build(deps-dev): bump types-protobuf from 4.23.0.2 to 4.23.0.3
Bumps [types-protobuf](https://github.com/python/typeshed) from 4.23.0.2 to 4.23.0.3.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-protobuf
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-07 14:42:39 +00:00
dependabot[bot]
358888178a build(deps-dev): bump flake8 from 6.0.0 to 6.1.0
Bumps [flake8](https://github.com/pycqa/flake8) from 6.0.0 to 6.1.0.
- [Commits](https://github.com/pycqa/flake8/compare/6.0.0...6.1.0)

---
updated-dependencies:
- dependency-name: flake8
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-07 14:42:32 +00:00
dependabot[bot]
57e393bf7a build(deps-dev): bump ruff from 0.0.280 to 0.0.282
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.0.280 to 0.0.282.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/BREAKING_CHANGES.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.0.280...v0.0.282)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-07 14:42:21 +00:00
Capa Bot
eb7aa63be6 Sync capa rules submodule 2023-08-07 13:54:49 +00:00
Capa Bot
298a07dc07 Sync capa rules submodule 2023-08-07 13:31:25 +00:00
Capa Bot
f50a5e8efc Sync capa rules submodule 2023-08-07 13:28:05 +00:00
Yacine
d06b33e7ea Merge pull request #1687 from mandiant/fix-lint
lint.py: add default backend
2023-08-07 14:16:11 +01:00
mr-tz
9660f1e5ab add default backend 2023-08-07 14:00:30 +02:00
Willi Ballenthin
74d9b06835 Merge pull request #1679 from Aayush-Goel-04/Aayush-Goel-04/Issue#1582
bump pydantic to 2.1.1
2023-08-07 12:02:53 +02:00
Willi Ballenthin
681d4fb007 Merge pull request #1678 from yelhamer/call-scope
Add a call scope
2023-08-07 11:31:29 +02:00
Yacine Elhamer
a185341a4d features/address.py: rename CallAddress DynamicCallAddress 2023-08-07 09:48:11 +01:00
Yacine Elhamer
aacd9f51b3 delete empty files 2023-08-07 09:48:11 +01:00
Yacine
95148d445a test_rules.py: update rules' formatting
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-08-07 09:47:57 +01:00
Yacine
65ac422e36 test_rules.py: update rules' fomratting
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-08-07 09:47:37 +01:00
Willi Ballenthin
5ffb6ca0cd Merge branch 'dynamic-feature-extraction' into call-scope 2023-08-07 10:40:53 +02:00
Willi Ballenthin
85f151303a merge 2023-08-07 08:40:03 +00:00
Willi Ballenthin
216cd01b3c sync test data submodule 2023-08-07 08:37:23 +00:00
Yacine
23bd2e7cd4 cape/call.py: remove use of the description keyword for features
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-08-07 09:13:07 +01:00
Willi Ballenthin
5de055e2af Merge pull request #1677 from mandiant/fix/add-devcontainer-pre-commit
devcontainer: install pre-commit hooks
2023-08-07 10:01:20 +02:00
Willi Ballenthin
dd870a5cbd Merge pull request #1676 from mandiant/fix/issue-1675
linter: skip native API check for NtProtectVirtualMemory
2023-08-07 10:00:59 +02:00
Aayush Goel
a2254852b0 Update CHANGELOG.md 2023-08-06 22:55:54 +05:30
Aayush Goel
17aad56800 Script to get unused features
Used show_features.py script
2023-08-06 22:53:50 +05:30
Yacine Elhamer
f461f65a86 move thread-scope features into the call-scope 2023-08-06 18:12:29 +01:00
Capa Bot
2c8f99143a Sync capa-testfiles submodule 2023-08-05 16:40:13 +00:00
Capa Bot
ee68031d19 Sync capa-testfiles submodule 2023-08-05 16:37:46 +00:00
Yacine Elhamer
8dc4adbb5e fix test_rules.py yaml identation bug 2023-08-04 16:20:37 +01:00
Yacine Elhamer
8b36cd1e35 add call-scope tests 2023-08-04 16:20:37 +01:00
Aayush Goel
851da25560 Update bulk-process.py 2023-08-04 10:43:34 +05:30
Aayush Goel
a4b00b9064 remove exclude_none = True to not drop none fields 2023-08-04 10:26:56 +05:30
Aayush Goel
fd61456164 Update capa/features/freeze/__init__.py
Co-authored-by: Willi Ballenthin <wballenthin@google.com>
2023-08-04 09:07:13 +05:30
Aayush Goel
261baca683 updated deperecated functions 2023-08-04 01:35:41 +05:30
Aayush Goel
c7dde262ed remove initial instantiation for fields. 2023-08-03 22:40:01 +05:30
Yacine
cd700a1782 Merge branch 'dynamic-feature-extraction' into call-scope 2023-08-03 15:27:44 +01:00
Yacine
60e94adeb1 base_extractor.py: fix ProcessHandle documentation comment
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-08-03 14:39:53 +01:00
Yacine
eafed0f1d4 build_statements(): fix call-scope InvalidRule message typo
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-08-03 14:38:38 +01:00
Yacine Elhamer
7c14c51012 cape/call.py: update extract_call_features() comment 2023-08-03 14:20:18 +01:00
Aayush Goel
2bed3468f6 bump pydantic to 2.1.1 2023-08-03 17:21:46 +05:30
Yacine Elhamer
4f9d24598f bugfix 2023-08-03 11:24:24 +01:00
Yacine Elhamer
4277b4bef8 include an address' parent in comparisons 2023-08-03 11:21:58 +01:00
colton-gabertan
bab6c978fb Merge branch 'master' into backend-ghidra 2023-08-02 16:17:19 -07:00
Yacine Elhamer
3c3205adf1 add call address to show-features.py script 2023-08-02 23:10:27 +01:00
Yacine Elhamer
4e1527df95 update changelog 2023-08-02 22:48:38 +01:00
Yacine Elhamer
ca2760fb46 Initial commit 2023-08-02 22:46:54 +01:00
Willi Ballenthin
6647ecb6d4 Merge branch 'master' into fix/add-devcontainer-pre-commit 2023-08-02 15:02:15 +02:00
Willi Ballenthin
13533074ea devcontainer: install pre-commit hooks 2023-08-02 13:01:15 +00:00
Willi Ballenthin
a538a7bbab linter: skip native API check for more UEFI routines 2023-08-02 12:55:22 +00:00
Willi Ballenthin
b2789f0df6 Merge branch 'master' into fix/issue-1675 2023-08-02 14:49:32 +02:00
Willi Ballenthin
ab5c8b1129 linter: skip native API check for NtEnumerateSystemEnvironmentValuesEx 2023-08-02 12:49:17 +00:00
Capa Bot
149983dced Sync capa rules submodule 2023-08-02 12:42:03 +00:00
Willi Ballenthin
04fbcbbbd3 linter: skip native API check for NtProtectVirtualMemory
closes #1675
2023-08-02 12:36:42 +00:00
Willi Ballenthin
727ece499a Merge pull request #1662 from Aayush-Goel-04/Aayush-Goel-04/Issue#1607
ELF: Implement file import and export name extractor
2023-08-02 13:15:32 +02:00
Aayush Goel
62f50265bc Resolved Import address 2023-08-02 16:41:24 +05:30
Capa Bot
95ffdf19ff Sync capa rules submodule 2023-08-02 11:03:52 +00:00
Capa Bot
d18224eac6 Sync capa-testfiles submodule 2023-08-02 11:03:16 +00:00
Aayush Goel
26935ee6e6 Update test_elffile_features.py 2023-08-02 13:51:51 +05:30
Aayush Goel
f8c499fb43 Added test for elf import/export handling 2023-08-02 11:52:27 +05:30
Willi Ballenthin
61924672e2 Merge pull request #1671 from yelhamer/rule-statement-building 2023-08-01 22:15:03 +02:00
Yacine Elhamer
7fdd988e4f remove redundant imports 2023-08-01 20:12:15 +01:00
Yacine Elhamer
a85e0523f8 remove Scopes LRU caching 2023-08-01 20:09:42 +01:00
Aayush Goel
3bb5754b66 Update elffile.py 2023-08-01 22:41:11 +05:30
Aayush Goel
dd2eef52c3 Update elffile.py
remove enumerate
2023-08-01 22:21:00 +05:30
Willi Ballenthin
da45fb4bea Merge branch 'master' into Aayush-Goel-04/Issue#1607 2023-08-01 16:34:42 +02:00
Willi Ballenthin
7ed517a8f3 Merge pull request #1668 from mandiant/dependabot/pip/types-tabulate-0.9.0.3
build(deps-dev): bump types-tabulate from 0.9.0.1 to 0.9.0.3
2023-08-01 16:33:42 +02:00
Willi Ballenthin
f00e7426c5 Merge pull request #1667 from mandiant/dependabot/pip/types-requests-2.31.0.2
build(deps-dev): bump types-requests from 2.31.0.1 to 2.31.0.2
2023-08-01 16:33:12 +02:00
Willi Ballenthin
3f29c61038 Merge branch 'master' into dependabot/pip/types-requests-2.31.0.2 2023-08-01 16:33:04 +02:00
Willi Ballenthin
647ce67f7e Merge pull request #1666 from mandiant/dependabot/pip/types-protobuf-4.23.0.2
build(deps-dev): bump types-protobuf from 4.23.0.1 to 4.23.0.2
2023-08-01 16:32:29 +02:00
Willi Ballenthin
224923b8bd Merge pull request #1665 from mandiant/dependabot/pip/pyyaml-6.0.1
build(deps-dev): bump pyyaml from 6.0 to 6.0.1
2023-08-01 16:31:41 +02:00
Willi Ballenthin
8a08a93b1c Merge branch 'master' into dependabot/pip/pyyaml-6.0.1 2023-08-01 16:29:15 +02:00
Capa Bot
ed98bb3a57 Sync capa rules submodule 2023-08-01 11:21:32 +00:00
Capa Bot
d12185d851 Sync capa-testfiles submodule 2023-08-01 11:21:02 +00:00
Capa Bot
5f8280eb09 Sync capa rules submodule 2023-08-01 11:16:09 +00:00
Yacine Elhamer
462024ad03 update tests to explicitely specify scopes 2023-08-01 07:41:47 +01:00
Yacine Elhamer
f0d09899a1 rules/__init__.py: invalidate rules with no scopes field 2023-08-01 07:19:11 +01:00
Aayush Goel
30abe40999 Merge branch 'mandiant:master' into Aayush-Goel-04/Issue#1607 2023-07-28 17:50:20 +05:30
Yacine Elhamer
b8212b3da7 main.py: replace | operator with Optional 2023-07-27 16:00:52 +01:00
Yacine Elhamer
3d812edc4d use weakrefs for Scopes instantiation; fix test_rules() 2023-07-27 15:52:39 +01:00
Yacine Elhamer
2efb7f2975 fix flake8 issues 2023-07-27 15:10:01 +01:00
Yacine Elhamer
44c5e96cf0 RuleSet: remove irrelevant rules after dependecies have been checked 2023-07-27 12:44:07 +01:00
Yacine Elhamer
97c878db22 update CHANGELOG 2023-07-27 10:33:34 +01:00
Yacine Elhamer
16e32f8441 add tests 2023-07-27 10:31:45 +01:00
Yacine Elhamer
d6aced5ec7 RulSet: add flavor-based rule filtering 2023-07-27 10:24:08 +01:00
colton-gabertan
0e58ec5176 Merge branch 'master' into backend-ghidra 2023-07-26 12:20:18 -07:00
Yacine Elhamer
b843382065 rules/__init__.py: update Scopes class 2023-07-26 17:20:51 +01:00
Willi Ballenthin
dd53349aea Merge pull request #1669 from xusheng6/master 2023-07-26 08:35:54 +02:00
Willi Ballenthin
d598faf145 Merge pull request #1664 from mandiant/dependabot/pip/ruff-0.0.280 2023-07-24 17:27:01 +02:00
dependabot[bot]
c265b1ca96 build(deps-dev): bump types-tabulate from 0.9.0.1 to 0.9.0.3
Bumps [types-tabulate](https://github.com/python/typeshed) from 0.9.0.1 to 0.9.0.3.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-tabulate
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-24 14:15:59 +00:00
dependabot[bot]
b554eaf563 build(deps-dev): bump types-requests from 2.31.0.1 to 2.31.0.2
Bumps [types-requests](https://github.com/python/typeshed) from 2.31.0.1 to 2.31.0.2.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-requests
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-24 14:15:55 +00:00
dependabot[bot]
3d51b84bd1 build(deps-dev): bump types-protobuf from 4.23.0.1 to 4.23.0.2
Bumps [types-protobuf](https://github.com/python/typeshed) from 4.23.0.1 to 4.23.0.2.
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-protobuf
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-24 14:15:50 +00:00
dependabot[bot]
684b2ded38 build(deps-dev): bump pyyaml from 6.0 to 6.0.1
Bumps [pyyaml](https://github.com/yaml/pyyaml) from 6.0 to 6.0.1.
- [Changelog](https://github.com/yaml/pyyaml/blob/6.0.1/CHANGES)
- [Commits](https://github.com/yaml/pyyaml/compare/6.0...6.0.1)

---
updated-dependencies:
- dependency-name: pyyaml
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-24 14:15:43 +00:00
dependabot[bot]
557e83b1dc build(deps-dev): bump ruff from 0.0.278 to 0.0.280
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.0.278 to 0.0.280.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/BREAKING_CHANGES.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.0.278...v0.0.280)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-24 14:15:37 +00:00
Xusheng
8f826cb92d Fix binja backend stack string detection. Re-enable binja stack string unit test 2023-07-24 19:15:35 +08:00
Aayush Goel
78a9909ec6 Update elffile.py
Updated changelog and added link references in comments
2023-07-23 15:30:37 +05:30
Willi Ballenthin
f4bdff0824 Merge pull request #1644 from yelhamer/find-dynamic-capabilities 2023-07-21 20:08:22 +02:00
Yacine Elhamer
d8c28e80eb add get_sample_hashes() to elf extractor 2023-07-21 15:50:09 +01:00
yelhamer
344b3e9931 Update capa/features/extractors/base_extractor.py
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-07-21 15:43:56 +01:00
yelhamer
c32ac19c0d Update capa/features/extractors/ida/extractor.py
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-07-21 15:43:41 +01:00
yelhamer
d13114e907 remove SampleHashes __iter__method
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-07-21 15:43:22 +01:00
yelhamer
90298fe2c8 Update capa/features/extractors/base_extractor.py
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-07-21 15:39:30 +01:00
Yacine Elhamer
3d1a1fb9fa add get_sample_hashes() to NullFeatureExtractor 2023-07-21 14:54:54 +01:00
Yacine Elhamer
830bad54bd fix bugs 2023-07-21 14:41:07 +01:00
Yacine Elhamer
c4ba5afe6b replace : FeatureSet annotations with a comment type annotation 2023-07-21 14:32:42 +01:00
Yacine Elhamer
4ec39d49aa fix linting issues 2023-07-21 14:03:57 +01:00
Yacine Elhamer
ab585ef951 add the skipif mark back 2023-07-21 14:00:58 +01:00
Yacine Elhamer
674122999f migrate the get_sample_hashes() function to each individual extractor 2023-07-21 14:00:01 +01:00
Yacine Elhamer
8085caef35 remove the usage of SampleHashes's __iter__() method 2023-07-21 13:48:48 +01:00
Yacine Elhamer
3ab3c61d5e use ida's hash-extraction functions 2023-07-21 13:48:48 +01:00
Yacine Elhamer
736b2cd689 address @mr-tz main.py review comments 2023-07-21 13:48:48 +01:00
yelhamer
bd8331678c update compute_static_layout with the appropriate types
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-07-21 13:16:51 +01:00
yelhamer
6f3fb42385 update compute_dynamic_layout with the appropriate type
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-07-21 13:15:55 +01:00
yelhamer
da4e887aee fix comment typo
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2023-07-21 12:40:02 +01:00
Yacine Elhamer
b1e468dae4 add tests for the get_sample_hashes() method 2023-07-21 11:04:21 +01:00
Yacine Elhamer
6d1a885864 update static freeze test 2023-07-21 08:48:18 +01:00
Yacine Elhamer
24b3abd706 add get_sample_hashes() to base extractor 2023-07-21 08:45:14 +01:00
yelhamer
806bc1853d Update mypy.ini: add TODO comment 2023-07-20 22:13:06 +01:00
Yacine Elhamer
6ee1dfd656 address review comments: rename SampleHashes's from_sample() method to from_bytes() method 2023-07-20 21:53:28 +01:00
Yacine Elhamer
ab092cb536 add sample_hashes attribute to the base extractors 2023-07-20 21:51:37 +01:00
Yacine Elhamer
b4cf50fb6e fix mypy issues 2023-07-20 21:48:05 +01:00
yelhamer
2b2b2b6545 Update capa/features/extractors/base_extractor.py
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-07-20 21:47:30 +01:00
yelhamer
fd7b926a33 Update capa/features/extractors/base_extractor.py
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-07-20 21:47:23 +01:00
Yacine Elhamer
482e0d386b use pathlib.Path() in binja and ida extractors 2023-07-20 21:42:14 +01:00
Yacine Elhamer
d99b16ed5e add copyright and remove old test 2023-07-20 21:41:16 +01:00
Yacine Elhamer
0a4fe58ac6 fix tests 2023-07-20 20:25:11 +01:00
Yacine Elhamer
8ac9caf45c fix bugs 2023-07-20 20:20:33 +01:00
Yacine Elhamer
1029b369f2 Merge remote-tracking branch 'parentrepo/dynamic-feature-extraction' into find-dynamic-capabilities 2023-07-20 20:02:49 +01:00
Willi Ballenthin
5ae588deaa Merge pull request #1658 from mandiant/sync-1657
sync
2023-07-20 14:05:22 +02:00
Willi Ballenthin
a2f31ab8ae update testfiles submodule 2023-07-20 11:52:15 +00:00
Willi Ballenthin
666c9c21a1 update testfiles submodule 2023-07-20 11:49:20 +00:00
Yacine Elhamer
a675c4c7a1 remove redundant code block 2023-07-20 11:27:07 +01:00
Yacine Elhamer
16eab6b5e5 remove unused commit 2023-07-20 11:24:07 +01:00
Yacine Elhamer
d520bfc753 fix bugs and add copyrights 2023-07-20 11:19:54 +01:00
Yacine Elhamer
301b10d261 fix style issues 2023-07-20 10:52:43 +01:00
Yacine Elhamer
e38e56ccf6 Merge remote-tracking branch 'parentrepo/dynamic-feature-extraction' into sync-1657 2023-07-20 09:33:48 +01:00
Mike Hunhoff
c0e126f812 merge upstream 2023-07-19 14:56:39 +00:00
yelhamer
7de223f116 Update capa/features/extractors/ida/extractor.py: add call to get_input_file_path()
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-07-19 15:39:06 +01:00
Capa Bot
4eabee7329 Sync capa rules submodule 2023-07-19 13:49:59 +00:00
Willi Ballenthin
0719273cee Merge pull request #1656 from RonnieSalomonsen/forward_export
rules: Add forwarded export characteristics to rule syntax under file…
2023-07-19 15:48:19 +02:00
Ronnie Salomonsen
de6bdf0621 Update CHANGELOG with fix for the new feature for forwarded export characteristics 2023-07-19 15:05:10 +02:00
Yacine Elhamer
c5d08ec0d1 update extractors and tests 2023-07-19 14:00:45 +01:00
Ronnie Salomonsen
1790dab1ab rules: Add forwarded export characteristics to rule syntax under file_scope 2023-07-19 11:27:52 +02:00
Yacine Elhamer
4e4b1235c3 mypy.ini: ignore proto issues 2023-07-18 21:04:51 +01:00
Yacine Elhamer
e5d7903475 add removed tests 2023-07-18 20:38:54 +01:00
Yacine Elhamer
bc46bf3202 add vverbose rendering 2023-07-18 11:26:20 +01:00
Colton Gabertan
6fa7f24818 Ghidra: Basic Block Feature Extraction (#1637)
* save progress

* implement loop detection

* implement recursive call detection

* lint repo

* fix python/java import errors

* simplify recursion detection

* implement tight loop extraction

* streamline loop detection, fix helper function signature

* begin stackstring extraction

* implement is_mov_imm_to_stack()

* implement stackstring extraction, fixture test passing

* clean & lint, pass fixture tests

* temp: resolve linting issues

* temp: fix linting issues

* implement reviewed changes, simplify functions

* fix tight loop extraction
2023-07-17 09:00:03 -06:00
yelhamer
4af84e53d5 bugfixes 2023-07-17 12:25:12 +01:00
Yacine Elhamer
e3f60ea0fb initial commit 2023-07-17 11:50:49 +01:00
Mike Hunhoff
68caece2fa fix linting errors 2023-07-13 18:49:52 +00:00
Mike Hunhoff
94aaaa297d remove stale is_runtime_ida function 2023-07-13 18:16:11 +00:00
Mike Hunhoff
6ce897e39b merge upstream 2023-07-13 17:57:34 +00:00
Mike Hunhoff
eeb0f78564 merge upstream 2023-07-12 17:57:35 +00:00
Moritz
ce15a2b01e Merge pull request #1580 from yelhamer/analysis-flavor
add flavored scopes
2023-07-12 17:24:38 +02:00
Colton Gabertan
97c2005661 Ghidra: Function Feature Extraction (#1597)
* save progress

* implement loop detection

* implement recursive call detection

* lint repo

* fix python/java import errors

* simplify recursion detection

* streamline loop detection, fix helper function signature
2023-07-12 08:58:35 -06:00
Yacine Elhamer
9c878458b8 fix typo: replace 'rules' with 'rule' 2023-07-12 15:43:32 +01:00
Yacine Elhamer
53d897da09 ida/plugin/form.py: replace list comprehension in any() with a generator 2023-07-12 15:39:56 +01:00
Yacine Elhamer
17030395c6 ida/plugin/form.py: replace usage of '==' with usage of 'in' operator 2023-07-12 15:36:28 +01:00
Yacine Elhamer
34d3d6c1f9 Merge remote-tracking branch 'origin/analysis-flavor' into yelhamer-analysis-flavor 2023-07-12 15:27:13 +01:00
Willi Ballenthin
e335c9f977 Merge pull request #1612 from yelhamer/process-thread-addresses
add process and thread addresses
2023-07-12 10:54:14 +02:00
Yacine Elhamer
4ee38cbe29 fix linting issues 2023-07-11 14:52:04 +01:00
Yacine Elhamer
12c9154f55 fix flake8 linting issues 2023-07-11 14:40:56 +01:00
Yacine Elhamer
0e312d6dfe replace unused variable 'r' with '_' 2023-07-11 14:38:52 +01:00
Yacine Elhamer
7e18eeddba update ruff.toml 2023-07-11 14:33:19 +01:00
Yacine Elhamer
0db7141e33 remove redundant import 2023-07-11 14:33:07 +01:00
Yacine Elhamer
1ef0b16f11 Update ruff.toml 2023-07-11 14:32:33 +01:00
Yacine Elhamer
37c1bf98eb fix ruff F401 pytes issues 2023-07-11 14:26:59 +01:00
Yacine Elhamer
85d4c00096 fix ruff linting issues with test_static_freeze 2023-07-11 14:07:08 +01:00
Yacine Elhamer
078978a5b5 fix fixtures issue 2023-07-11 13:33:48 +01:00
Yacine Elhamer
841d393f8b fix non-matching type issue 2023-07-11 12:49:15 +01:00
Yacine Elhamer
740d1f6d4e fix imports: import TypeAlias from typing_extensions 2023-07-11 12:40:58 +01:00
Yacine Elhamer
b615c103ef fix flake8 linting: replace unused 'variable' with '_' 2023-07-11 12:37:01 +01:00
Yacine Elhamer
f879f53a6b fix linting issues 2023-07-11 12:33:37 +01:00
Yacine Elhamer
42baa10bcb Merge branch 'process-thread-addresses' of https://github.com/yelhamer/capa into yelhamer-process-thread-addresses 2023-07-11 12:07:20 +01:00
Yacine Elhamer
6feb9f540f fix ruff linting issues 2023-07-11 10:58:00 +01:00
Yacine Elhamer
f86ecfe446 Merge remote-tracking branch 'parentrepo/dynamic-feature-extraction' into analysis-flavor 2023-07-11 10:43:31 +01:00
colton-gabertan
785825d77e Merge branch 'master' into backend-ghidra 2023-07-11 01:00:55 -07:00
Yacine Elhamer
64a16314ab Update capa/features/address.py
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2023-07-10 16:24:30 +01:00
Yacine Elhamer
dccebaeff8 Update CHANGELOG.md: include PR number 2023-07-10 16:18:59 +01:00
Yacine Elhamer
d2e5dea3e2 update magic header 2023-07-10 16:15:37 +01:00
Yacine Elhamer
ec59886031 Update capa/rules/__init__.py
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-07-10 15:58:27 +01:00
Yacine Elhamer
917dd8b0db Update scripts/lint.py
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-07-10 15:58:17 +01:00
Yacine Elhamer
63e273efd4 fix bugs and mypy issues 2023-07-10 15:52:33 +01:00
Yacine Elhamer
9394194031 address review comments 2023-07-10 14:12:56 +01:00
Yacine Elhamer
af256bc0e9 fix mypy issues and bugs 2023-07-10 14:11:10 +01:00
Yacine Elhamer
37e4b913b0 address review comments 2023-07-10 13:22:47 +01:00
Yacine Elhamer
722ee2f3d0 remove redundant print
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-07-10 12:54:15 +01:00
Yacine Elhamer
e5f5d542d0 replace ppid and pid fields with process in thread address
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-07-10 12:53:27 +01:00
Yacine Elhamer
1ac64aca10 feature freeze: fix Addres.from_capa() not returning bug 2023-07-10 12:44:27 +01:00
Yacine Elhamer
78054eea5a update changelog 2023-07-10 12:18:16 +01:00
Yacine Elhamer
ff63b0ff1a rename test_freeze.py to test_static_freeze.py 2023-07-10 12:15:38 +01:00
Yacine Elhamer
e2e367f091 update tests 2023-07-10 12:15:06 +01:00
Yacine Elhamer
5aa1a1afc7 initial commit: add ProcessAddress and ThreadAddress 2023-07-10 12:14:53 +01:00
Willi Ballenthin
a2d6bd693b Merge branch 'dynamic-feature-extraction' into analysis-flavor 2023-07-10 10:23:49 +02:00
Willi Ballenthin
7f57fccefb fix lints after sync with master 2023-07-10 02:55:50 +02:00
Willi Ballenthin
72e123e319 sync master 2023-07-10 02:50:18 +02:00
Willi Ballenthin
d29e7140b6 Merge pull request #1596 from mandiant/sync-master
Sync master
2023-07-10 10:30:23 +02:00
colton-gabertan
d452fdeca5 Merge branch 'master' into backend-ghidra 2023-07-08 00:20:47 -07:00
mr-tz
b6580f99db sync submodule 2023-07-07 19:37:25 +02:00
Yacine Elhamer
605fbaf803 add import asdict from dataclasses 2023-07-07 15:33:05 +01:00
Yacine Elhamer
03b0493d29 Scopes class: remove __eq__ operator overriding and override __in__ instead 2023-07-07 15:31:45 +01:00
Yacine Elhamer
5e295f59a4 DEV_SCOPE: add todo comment 2023-07-07 15:31:45 +01:00
mr-tz
f3135630d1 Merge branch 'master' into sync-master 2023-07-07 14:28:13 +02:00
Moritz
e140fba5df enhance various dynamic-related functions (#1590)
* enhance various dynamic-related functions

* test_cape_features(): update API(NtQueryValueKey) feature count to 7

---------

Co-authored-by: Yacine Elhamer <elhamer.yacine@gmail.com>
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-07-07 13:59:12 +02:00
Yacine Elhamer
fa7a7c294e replace usage of __dict__ with dataclasses.asdict()
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-07-07 11:01:02 +01:00
Yacine Elhamer
9dd65bfcb9 extract_subscope_rules(): use DEV_SCOPE 2023-07-07 08:54:19 +01:00
Colton Gabertan
51ffb1d75c Add Ghidra File Feature Extraction (#1564)
Implement Ghidra backend file feature extraction
2023-07-06 17:05:08 -07:00
colton-gabertan
1f631b3ed1 bump min Python3 version to 3.8 2023-07-06 15:42:04 -07:00
colton-gabertan
1ea91d60ac Merge branch 'master' into backend-ghidra 2023-07-06 15:40:09 -07:00
Yacine Elhamer
a8f722c4de xfail tests that require the old ruleset 2023-07-06 18:15:02 +01:00
Yacine Elhamer
0c56291e4a update linter 2023-07-06 17:50:57 +01:00
Yacine Elhamer
c916e3b07f update the linter 2023-07-06 17:27:45 +01:00
Yacine Elhamer
32f936ce8c address review comments 2023-07-06 17:17:18 +01:00
Colton Gabertan
c5f51e03f4 ghidra: Add Global Feature Extraction (#1526)
* Revert "colton: removed redundant imports & object, locally tested"

This reverts commit 3da233dcad.

* removed redundant imports & objects, local test confirmation

* linted with isort

* linted with black

* linted with pycodestyle

* additional linting

* rebasing to avoid merge conflicts
2023-07-06 01:27:37 -07:00
Colton Gabertan
855463b319 Add Ghidra Backend CI configuration, fix CHANGELOG (#1529)
* ghidra-backend ci working, fix CHANGELOG

* temp: Add backend-ghidra to CI test workflow & add versioning to matrix

* lint to avoid failure

* linting for CI

* cleanup CI, integrate actions, simplify installations

* fix gradle repo

* fix typo

* fix submodule checkout for rules & test data

* fix relative test data path

* remove unnecessary steps

* add flag to mkdir to resolve pipeline failure
2023-07-05 18:48:45 -06:00
Yacine Elhamer
47aebcbdd4 fix show-capabilities-by-function 2023-07-06 00:48:22 +01:00
Yacine Elhamer
4649c9a61d rename rule.scope to rule.scope in ida plugin 2023-07-06 00:09:23 +01:00
Yacine Elhamer
9300e68225 fix mypy issues in test_rules.py 2023-07-06 00:05:20 +01:00
Yacine Elhamer
19e40a3383 address review comments 2023-07-05 23:58:08 +01:00
Yacine Elhamer
9ffe85fd9c build_statements: add support for scope flavors 2023-07-05 15:57:57 +01:00
Yacine Elhamer
8ba86e9cea add update Scopes class and switch scope to scopes 2023-07-05 15:00:14 +01:00
Yacine Elhamer
c042a28af1 rename Flavor to Scopes 2023-07-03 19:21:08 +01:00
Yacine Elhamer
1b59efc79a Apply suggestions from code review: rename Flavor to Scopes
Co-authored-by: Willi Ballenthin (Google) <118457858+wballenthin@users.noreply.github.com>
2023-07-03 11:11:14 +01:00
Yacine Elhamer
f1d7ac36eb Update test_rules.py 2023-07-03 02:48:24 +01:00
Yacine Elhamer
21cecb2aec tests: add unit tests for flavored scopes 2023-07-01 01:51:44 +01:00
Yacine Elhamer
8a93a06b71 fix mypy issues 2023-07-01 01:41:19 +01:00
Yacine Elhamer
d2ff0af34a Revert "tests: add unit tests for flavored scopes"
This reverts commit 6f0566581e.
2023-07-01 01:39:54 +01:00
Yacine Elhamer
ae5f2ec104 fix mypy issues 2023-07-01 01:38:37 +01:00
Yacine Elhamer
6f0566581e tests: add unit tests for flavored scopes 2023-07-01 00:57:01 +01:00
Yacine Elhamer
e726c7894c ensure_feature_valid_for_scope(): add support for flavored scopes 2023-07-01 00:56:35 +01:00
Yacine Elhamer
c4bb4d9508 update changelog 2023-06-30 20:28:40 +01:00
Yacine Elhamer
cfad228d3c scope flavors: add a Flavor class 2023-06-30 20:26:55 +01:00
Willi Ballenthin
670faf1d1d Merge pull request #1576 from yelhamer/process-scope 2023-06-28 16:34:15 +02:00
Yacine Elhamer
659163a93c thread scope: fix feature inheritance error 2023-06-28 14:52:00 +01:00
Yacine Elhamer
2b163edc0e add thread scope 2023-06-28 13:08:11 +01:00
Yacine Elhamer
0d38f85db7 process scope: add MatchedRule feature 2023-06-28 11:27:08 +01:00
Willi Ballenthin
1dc2825a75 Merge pull request #1577 from mandiant/master
sync dynamic-feature-extraction
2023-06-28 11:16:01 +02:00
Willi Ballenthin
630e2d23c9 Merge pull request #1569 from yelhamer/static-extractor
add a StaticFeatureExtractor class
2023-06-28 11:13:46 +02:00
Yacine Elhamer
c73187e7d4 Update capa/rules/__init__.py
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2023-06-28 10:08:29 +01:00
Yacine Elhamer
e18afe5d1e Merge branch 'dynamic-feature-extraction' into process-scope 2023-06-28 01:46:39 +01:00
Yacine Elhamer
7534e3f739 update changelog 2023-06-28 01:41:13 +01:00
Yacine Elhamer
0e01d91cec update changelog 2023-06-28 01:39:11 +01:00
Yacine Elhamer
06aea6b97c fix mypy and codestyle issues 2023-06-27 11:32:21 +01:00
Yacine Elhamer
a99ff813cb DynamicFeatureExtractor: remove get_base_address() method
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-06-27 11:22:35 +01:00
Yacine Elhamer
92734416a6 update base_extractor.py example
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-06-27 11:20:41 +01:00
Yacine Elhamer
2f32d4fe49 Update base_extractor.py with review comments
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-06-27 11:20:02 +01:00
Willi Ballenthin
81d35eb645 Merge branch 'dynamic-feature-extraction' into static-extractor 2023-06-27 09:42:16 +02:00
Willi Ballenthin
ac24ac2507 Merge pull request #1566 from yelhamer/dynamic-show-features
integrate the CAPE extractor with the show-features.py script
2023-06-27 09:37:27 +02:00
Yacine Elhamer
b172f9a354 FeatureExtractor alias: fix mypy typing issues by adding ininstance-based assert statements 2023-06-26 22:46:27 +01:00
Yacine Elhamer
63e4d3d5eb fix TypeAlias importing: import from typing_extensions to support Python 3.9 and lower 2023-06-26 21:14:17 +01:00
Yacine Elhamer
c74c8871f8 scripts: add type-related assert statements 2023-06-26 21:06:35 +01:00
Yacine Elhamer
3f5d08aedb base_extractor.py: add TypeAlias keyword, use union instead of bar operator, add an extract_file_features() and extract_global_features() methods 2023-06-26 20:57:51 +01:00
Yacine Elhamer
ddcb299834 main.py: address review suggestions (using elif for type casts, renaming to find_static_capabilities()) 2023-06-26 20:53:41 +01:00
Yacine Elhamer
a9f70dd1e5 main.py: update extractor type casting
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-06-26 20:01:30 +01:00
Yacine Elhamer
aff0c6b49b show-featurex.py: bugfix in ida_main() 2023-06-26 09:41:14 +01:00
Yacine Elhamer
417bb42ac8 show_features.py: rename show_{function,process}_features to show_{static,dynamic}_features.py 2023-06-26 09:16:59 +01:00
Yacine Elhamer
040ed4fa57 get_format_from_report(): use strings instead of literals
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2023-06-26 09:05:20 +01:00
Yacine Elhamer
94fc7b4e9a FeatureExtractor alias: add type casts to either StaticFeatureExtractor or DynamicFeatureExtractor 2023-06-26 01:23:01 +01:00
Yacine Elhamer
172e7a7649 update changelog 2023-06-25 23:03:13 +01:00
Yacine Elhamer
37ed138dcf base_extractor(): add a StaticFeatureExtractor and DynamicFeatureExtractor base classes, as well as a FeatureExtractor type alias 2023-06-25 22:57:39 +01:00
Yacine Elhamer
5f6aade92b get_format_from_report(): fix bugs and add a list of dynamic formats 2023-06-25 00:54:55 +01:00
Yacine Elhamer
0c62a5736e add support for determining the format of a sandbox report 2023-06-24 23:51:12 +01:00
Yacine Elhamer
f1406c1ffd scripts/show-features.py: prefix {static,dynamic}_analysis() functions' name with 'print_' 2023-06-23 13:58:34 +01:00
Yacine Elhamer
1cdc3e5232 fix codestyle 2023-06-23 13:48:49 +01:00
Yacine Elhamer
bd9870254e Apply suggestions from code review: use EXTENSIONS_CAPE, and ident 'thread' by one more space 2023-06-23 13:31:35 +01:00
Yacine Elhamer
0442b8c1e1 Apply suggestions from code review: use is_ for booleans
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-06-23 13:27:20 +01:00
Yacine Elhamer
585876d6af capa/main.py: use "rb" for opening json files
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-06-23 13:25:37 +01:00
Yacine Elhamer
902d726ea6 capa/main.py: change json import positioning to start of the file 2023-06-22 23:57:03 +01:00
Yacine Elhamer
3f35b426dd Apply suggestions from code review
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2023-06-22 21:58:01 +01:00
Yacine Elhamer
761d861888 Update fixtures.py samples path 2023-06-22 16:55:00 +01:00
Yacine Elhamer
9f185ed5c0 remove incompatible bar union syntax 2023-06-22 15:59:23 +01:00
Yacine Elhamer
63b2077335 get_extractor(): set return type to FeatureExtractor, and cast into the appropriate class before each usage 2023-06-22 15:55:24 +01:00
Yacine Elhamer
12d5beec6e add type cast to fix get_extractor() typing issues 2023-06-22 15:51:56 +01:00
Yacine Elhamer
b77e68df19 fix codestyle and typing 2023-06-22 14:17:06 +01:00
Yacine Elhamer
fcdd4fa410 update changelog 2023-06-22 14:03:01 +01:00
Yacine Elhamer
07c48bca68 scripts/show-features.py: add dynamic feature extraction from cape reports 2023-06-22 13:56:54 +01:00
Yacine Elhamer
79ff76d124 main.py: fix bugs for adding the cape extractor/format 2023-06-22 13:55:50 +01:00
Yacine Elhamer
de2ba1ca94 add the cape report format to main and across several other locations 2023-06-22 12:55:39 +01:00
Yacine Elhamer
45002bd51d Revert "scripts/show-features.py: add dynamic feature extraction from cape reports"
This reverts commit 64189a4d08.
2023-06-22 12:29:51 +01:00
Yacine Elhamer
be7ebad956 Revert "tests/fixtures.py: update path forming for the cape sample"
This reverts commit 6712801b01.
2023-06-22 12:18:34 +01:00
Yacine Elhamer
64189a4d08 scripts/show-features.py: add dynamic feature extraction from cape reports 2023-06-22 12:16:31 +01:00
Willi Ballenthin
708cb28ed0 Merge pull request #1546 from yelhamer/cape-extractor
add the CAPE feature extractor
2023-06-21 09:33:26 +02:00
Yacine Elhamer
6712801b01 tests/fixtures.py: update path forming for the cape sample
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-06-20 20:30:06 +01:00
Yacine Elhamer
f29db693c8 fix git submodules error 2023-06-20 20:25:19 +01:00
Yacine Elhamer
0502bfd95d remove cape report from get_md5_hash() function 2023-06-20 20:24:38 +01:00
Yacine Elhamer
78a3901c61 cape/helpers.py: add a find_process() function for quick-fetching processes from the cape report 2023-06-20 15:59:22 +01:00
Yacine Elhamer
0a4e3008af fixtures.py: update CAPE's feature count and presence tests 2023-06-20 13:51:16 +01:00
Yacine Elhamer
d03ba5394f cape/global_.py: add warning messages if architecture/os/format are unknown 2023-06-20 13:26:25 +01:00
Yacine Elhamer
2262e6c7d0 Merge branch 'test-cape-extractor' into cape-extractor 2023-06-20 13:22:15 +01:00
Yacine Elhamer
31a349b13b cape feature tests: fix feature count function typo 2023-06-20 13:21:52 +01:00
Yacine Elhamer
1ba143ef26 Merge branch 'test-cape-extractor' into cape-extractor 2023-06-20 13:20:49 +01:00
Yacine Elhamer
1532ce1bab add tests for extracting argument values 2023-06-20 13:20:33 +01:00
Yacine Elhamer
fa9b920b71 cape/thread.py: do not extract return values, and extract argument values as Strings 2023-06-20 13:17:53 +01:00
Yacine Elhamer
40b2d5f724 add a remote origin to submodule, and switch to that branch 2023-06-20 12:40:47 +01:00
Yacine Elhamer
0623a5a8de point capa-testfiles submodule towards dynamic-feautre-extractor branch 2023-06-20 12:13:57 +01:00
Yacine Elhamer
cfa1d08e7e update testfiles submodule to point at dev branch 2023-06-20 11:28:40 +01:00
Yacine Elhamer
6196814672 cape/file.py: fix KeyError bug 2023-06-20 10:51:18 +01:00
Yacine Elhamer
f5af2bf393 Merge branch 'test-cape-extractor' into cape-extractor 2023-06-20 10:47:56 +01:00
Yacine Elhamer
374fb033c1 add support for gzip compressed cape samples, and fix QakBot sample path 2023-06-20 10:29:52 +01:00
Yacine Elhamer
4db80e75a4 add mode and encoding parameters to open() 2023-06-20 10:13:06 +01:00
Yacine Elhamer
8547277958 tests/fixtures.py bugfix: remove redundant lambda function
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2023-06-20 10:10:42 +01:00
Yacine Elhamer
ec3366b0e5 Update tests/fixtures.py
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2023-06-20 10:09:27 +01:00
Yacine Elhamer
48bd04b387 tests/fixtures.py: return direct extractor with no intermediate variable
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2023-06-20 10:09:00 +01:00
Yacine Elhamer
41a481252c Update CHANGELOG.md
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2023-06-20 10:08:12 +01:00
Yacine Elhamer
a7cf3b5b10 features/insn.py: revert added strace-based API feature 2023-06-20 10:04:37 +01:00
Yacine Elhamer
ba63188f27 cape/file.py: fix bug in call to helpers.generate_symbols()
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-06-20 10:02:57 +01:00
Yacine Elhamer
9cc34cb70f cape/file.py: fix imports ordering and format 2023-06-20 00:19:55 +01:00
Yacine Elhamer
b9a4d72b42 cape/file.py: add usage of helpers.generate_symbols() 2023-06-20 00:12:21 +01:00
Yacine Elhamer
8eef210547 update changelog 2023-06-19 23:57:51 +01:00
Yacine Elhamer
ef999ed954 rules/__init__.py: remove redundant HBI features 2023-06-19 23:56:10 +01:00
Yacine Elhamer
33de609560 Revert "removed redundant HBI features"
This reverts commit c88f859dae.
2023-06-19 23:55:22 +01:00
Yacine Elhamer
624151c3f7 Revert "update changelog"
This reverts commit 49b77d5477.
2023-06-19 23:55:12 +01:00
Yacine Elhamer
c88f859dae removed redundant HBI features 2023-06-19 23:55:06 +01:00
Yacine Elhamer
49b77d5477 update changelog 2023-06-19 23:49:19 +01:00
Yacine Elhamer
d4c4a17eb7 bugfixes and add cape sample tests 2023-06-19 23:42:27 +01:00
Yacine Elhamer
3c8abab574 fix bugs and refactor code 2023-06-19 23:40:09 +01:00
Yacine Elhamer
38596f8d0e add features for the QakBot sample 2023-06-19 19:32:56 +01:00
Yacine Elhamer
4acdca090d bug fixes 2023-06-19 17:14:59 +01:00
Yacine Elhamer
f02178852b update changelog 2023-06-19 17:01:05 +01:00
Yacine Elhamer
98e7acddf4 fix codestyle issues 2023-06-19 16:59:27 +01:00
Yacine Elhamer
9458e851c0 update test sample's path 2023-06-19 16:46:24 +01:00
Yacine Elhamer
a04512d7b8 add unit tests for the cape feature extractor 2023-06-19 16:43:54 +01:00
Yacine Elhamer
d6fa832d83 cape: move get_processes() method to file scope 2023-06-19 13:50:46 +01:00
Yacine Elhamer
dbad921fa5 code style changes 2023-06-15 13:21:17 +01:00
Yacine Elhamer
e1535dd574 remove Registry, Filename, and mutex features 2023-06-15 13:17:07 +01:00
Yacine Elhamer
22640eb900 cape/file.py: remove FunctionName feature extraction for imported functions 2023-06-15 12:44:57 +01:00
Yacine Elhamer
7e51e03043 cape/file.py: remove String, Filename, and Mutex features 2023-06-15 12:43:39 +01:00
Yacine Elhamer
865616284f cape/thread.py: remove yielding argument features
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-06-15 12:33:22 +01:00
Yacine Elhamer
0cf728b7e1 global_.py: update typo in yielded OS name
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-06-15 12:28:08 +01:00
Willi Ballenthin
a2d563b081 Merge branch 'dynamic-feature-extraction' into cape-extractor 2023-06-15 12:43:55 +02:00
Willi Ballenthin
8119aa6933 ci: do tests on dynamic-feature-extraction branch 2023-06-15 12:17:02 +02:00
Willi Ballenthin
6b953363d1 Update capa/features/extractors/base_extractor.py 2023-06-15 11:40:33 +02:00
Willi Ballenthin
139b240250 Update capa/features/extractors/base_extractor.py 2023-06-15 11:40:32 +02:00
Willi Ballenthin
36b5dff1f0 Update capa/features/extractors/base_extractor.py 2023-06-15 11:40:32 +02:00
Yacine Elhamer
7ae07d4de5 remove redundant types
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-06-15 11:40:32 +02:00
Yacine Elhamer
59ef52a271 remove default implementation
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-06-15 11:40:31 +02:00
Yacine Elhamer
34a1b22a38 remove ppid member from ProcessHandle
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-06-15 11:40:31 +02:00
Yacine Elhamer
b4f01fa6c2 add ppid documentation to the dynamic extractor interface 2023-06-15 11:40:30 +02:00
Yacine Elhamer
2d6d16dcd0 add parent process id to the process handle 2023-06-15 11:40:30 +02:00
Yacine Elhamer
1ccae4fef2 remove from_trace() and submit_sample() methods 2023-06-15 11:40:29 +02:00
Yacine Elhamer
ee30acab32 get_threads(): fix mypy typing
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2023-06-15 11:40:29 +02:00
Yacine Elhamer
5189bef325 fix bad comment
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2023-06-15 11:40:28 +02:00
Yacine Elhamer
17597580f4 add abstract DynamicExtractor class 2023-06-15 11:40:28 +02:00
Yacine Elhamer
f97f9e8646 Merge branch 'dynamic-features' into cape-extractor 2023-06-14 23:07:39 +01:00
Yacine Elhamer
91f1d41324 extract registry keys, files, and mutexes from the sample 2023-06-14 22:57:41 +01:00
Yacine Elhamer
d9d9d98ea0 update the Registry, Filename, and Mutex classes 2023-06-14 22:45:12 +01:00
Willi Ballenthin
e7115c7316 Update capa/features/extractors/base_extractor.py 2023-06-14 22:43:37 +01:00
Willi Ballenthin
6c58e26f14 Update capa/features/extractors/base_extractor.py 2023-06-14 22:43:37 +01:00
Willi Ballenthin
dc371580a5 Update capa/features/extractors/base_extractor.py 2023-06-14 22:43:37 +01:00
Yacine Elhamer
2a047073e9 remove redundant types
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-06-14 22:43:37 +01:00
Stephen Eckels
6e3b1bc240 explorer: optimize cache and extractor interface (#1470)
* Optimize cache and extractor interface

* Update changelog

* Run linter formatters

* Implement review feedback

* Move rulegen extractor construction to tab change

* Change rulegen cache construction behavior

* Adjust return values for CR, format

* Fix mypy errors

* Format

* Fix merge

---------

Co-authored-by: Stephen Eckels <stephen.eckels@mandiant.com>
2023-06-14 22:43:37 +01:00
Capa Bot
51faaae1d0 Sync capa rules submodule 2023-06-14 22:43:37 +01:00
Capa Bot
f55804ef06 Sync capa rules submodule 2023-06-14 22:43:37 +01:00
Xusheng
e671e1c87c Add a test that asserts on the binja version 2023-06-14 22:43:37 +01:00
Xusheng
a7aa817dce Update the stack string detection with BN's builtin outlining of constant expressions 2023-06-14 22:43:37 +01:00
Capa Bot
dcce4db6d5 Sync capa rules submodule 2023-06-14 22:43:37 +01:00
Yacine Elhamer
64c4f0f1aa remove default implementation
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-06-14 22:43:37 +01:00
Yacine Elhamer
a8f928200b remove ppid member from ProcessHandle
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-06-14 22:43:37 +01:00
Yacine Elhamer
58d42b09d9 add ppid documentation to the dynamic extractor interface 2023-06-14 22:43:37 +01:00
Yacine Elhamer
0cd481b149 remove redundant comments
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2023-06-14 22:42:25 +01:00
Yacine Elhamer
a66c55ca14 add the initial version of the cape extractor 2023-06-14 22:34:11 +01:00
Yacine Elhamer
18715dbe2e fix typo bug 2023-06-14 21:47:40 +01:00
Willi Ballenthin
23dee61389 Merge branch 'dynamic-feature-extraction' into cape-extractor 2023-06-14 12:41:08 +02:00
Willi Ballenthin
23dc3f29cd Merge pull request #1528 from yelhamer/dynamic-extractor
add a Dynamic extractor interface
2023-06-14 11:00:06 +02:00
Willi Ballenthin
4c701f4b6c Update capa/features/extractors/base_extractor.py 2023-06-14 10:59:07 +02:00
Willi Ballenthin
7a94f524b4 Update capa/features/extractors/base_extractor.py 2023-06-14 10:58:59 +02:00
Willi Ballenthin
23deb41436 Update capa/features/extractors/base_extractor.py 2023-06-14 10:58:50 +02:00
Yacine Elhamer
7198ebefc9 remove redundant types
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-06-14 09:58:33 +01:00
Willi Ballenthin
32cb57532e Merge branch 'dynamic-feature-extraction' into dynamic-extractor 2023-06-14 10:54:44 +02:00
Yacine Elhamer
edcfece993 remove default implementation
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-06-14 09:33:24 +01:00
Yacine Elhamer
baf209f3cc remove ppid member from ProcessHandle
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-06-14 09:33:07 +01:00
Yacine Elhamer
ece47c9ed5 add ppid documentation to the dynamic extractor interface 2023-06-14 09:05:53 +01:00
Yacine Elhamer
3d40ed968a Merge branch 'dynamic-features' into cape-extractor 2023-06-13 23:04:44 +01:00
Yacine Elhamer
10f56de5e8 Merge branch 'dynamic-extractor' into dynamic-features 2023-06-13 23:03:33 +01:00
Yacine Elhamer
5ee4fc2cd5 add parent process id to the process handle 2023-06-13 23:02:00 +01:00
Yacine Elhamer
a7917a0f3d add cape's thread features' extraction module 2023-06-13 22:56:15 +01:00
Yacine Elhamer
0274cf3ec7 add cape's global features' extraction module 2023-06-13 22:55:42 +01:00
Yacine Elhamer
3aa7c96902 add cape extractor class 2023-06-13 22:54:52 +01:00
Yacine Elhamer
ffa1851bbf Merge branch 'dynamic-features' into cape-extractor 2023-06-13 14:26:34 +01:00
Yacine Elhamer
45c3345bbc Merge branch 'dynamic-extractor' into dynamic-features 2023-06-13 14:26:14 +01:00
Yacine Elhamer
a6ca3aaa66 remove from_trace() and submit_sample() methods 2023-06-13 14:23:50 +01:00
Yacine Elhamer
5a10b612a1 add a Mutex feature 2023-06-12 00:06:53 +01:00
Yacine Elhamer
632b3ff07c add a Filename feature 2023-06-12 00:06:05 +01:00
Yacine Elhamer
efe1d1c0ac add a Registry feature 2023-06-12 00:05:20 +01:00
Yacine Elhamer
86e2f83a7d extend the API feature to support an strace-like argument style 2023-06-11 23:19:24 +01:00
Yacine Elhamer
a2b3a38f86 add the cape extractor's file hierarchy 2023-06-10 20:06:57 +01:00
Yacine Elhamer
f243749d38 get_threads(): fix mypy typing
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2023-06-09 09:03:49 +00:00
Yacine Elhamer
dac103c621 fix bad comment
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2023-06-09 09:03:09 +00:00
Yacine Elhamer
35e53e9691 add abstract DynamicExtractor class 2023-06-08 23:15:29 +00:00
colton-gabertan
3da233dcad colton: removed redundant imports & object, locally tested 2023-06-07 13:04:49 -07:00
Colton Gabertan
a7988a6e78 Merge pull request #1514 from colton-gabertan/master
New Feature: Ghidra Backend - Initial Merge
2023-06-02 23:40:23 -07:00
Colton Gabertan
de19c9300d Merge pull request #1 from colton-gabertan/ghidra_backend
Ghidra backend
2023-06-02 23:24:43 -07:00
colton-gabertan
a7639d33b9 colton: update CHANGELOG 2023-06-02 23:11:18 -07:00
Colton Gabertan
c3f9c27e34 Merge branch 'mandiant:master' into ghidra_backend 2023-06-02 22:42:35 -07:00
colton-gabertan
b849cfd4a5 ghidra ci setup, test files in development 2023-06-02 22:41:29 -07:00
colton-gabertan
16444fe5ed first working CI install 2023-06-01 11:24:21 -07:00
colton-gabertan
5af1a42bf1 reverting tests.yml 2023-05-29 20:24:37 -07:00
colton-gabertan
73183e9c19 run tests.yml on workflow dispatch 2023-05-29 20:16:10 -07:00
colton-gabertan
b35cfdaf6a workflow_dispatch - temp 2023-05-29 20:13:35 -07:00
colton-gabertan
8c40e82796 configuring runner for ghidra tests 2023-05-29 19:58:59 -07:00
colton-gabertan
78bd5e1e3b colton: tests.yml installs Java, Ghidra, and Ghidrathon 2023-05-28 19:04:31 -07:00
colton-gabertan
50afc2f9b2 colton: developing ghidra backend tests 2023-05-26 17:51:48 -07:00
colton-gabertan
ffe089d444 colton: GhidraFeatureExtractor constructor pulls OS & Arch 2023-05-19 19:10:39 -07:00
colton-gabertan
1f09c92306 colton: OS extraction functionality implemented 2023-05-19 18:38:13 -07:00
colton-gabertan
14b0c5fdbf colton: ghidra runtime detection & GhidraFeatureExtractor 2023-05-19 14:38:55 -07:00
316 changed files with 65145 additions and 4622 deletions

View File

@@ -1,6 +1,6 @@
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.233.0/containers/python-3/.devcontainer/base.Dockerfile
# [Choice] Python version (use -bullseye variants on local arm64/Apple Silicon): 3, 3.10, 3.9, 3.8, 3.7, 3.6, 3-bullseye, 3.10-bullseye, 3.9-bullseye, 3.8-bullseye, 3.7-bullseye, 3.6-bullseye, 3-buster, 3.10-buster, 3.9-buster, 3.8-buster, 3.7-buster, 3.6-buster
# [Choice] Python version (use -bullseye variants on local arm64/Apple Silicon): 3, 3.10, 3-bullseye, 3.10-bullseye, 3-buster, 3.10-buster, etc.
ARG VARIANT="3.10-bullseye"
FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT}

View File

@@ -6,7 +6,7 @@
"dockerfile": "Dockerfile",
"context": "..",
"args": {
// Update 'VARIANT' to pick a Python version: 3, 3.10, 3.9, 3.8, 3.7, 3.6
// Update 'VARIANT' to pick a Python version: 3, 3.10, etc.
// Append -bullseye or -buster to pin to an OS version.
// Use -bullseye variants on local on arm64/Apple Silicon.
"VARIANT": "3.10",
@@ -41,7 +41,7 @@
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "git submodule update --init && pip3 install --user -e .[dev]",
"postCreateCommand": "git submodule update --init && pip3 install --user -e .[dev] && pre-commit install",
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "vscode",

View File

@@ -57,7 +57,7 @@ When we make a significant decision in how we maintain the project and what we c
we will document it in the [capa issues tracker](https://github.com/mandiant/capa/issues).
This is the best place review our discussions about what/how/why we do things in the project.
If you have a question, check to see if it is documented there.
If it is *not* documented there, or you can't find an answer, please open a issue.
If it is *not* documented there, or you can't find an answer, please open an issue.
We'll link to existing issues when appropriate to keep discussions in one place.
## How Can I Contribute?

View File

@@ -4,3 +4,6 @@ updates:
directory: "/"
schedule:
interval: "weekly"
ignore:
- dependency-name: "*"
update-types: ["version-update:semver-patch"]

4
.github/flake8.ini vendored
View File

@@ -10,6 +10,8 @@ extend-ignore =
F811,
# E501 line too long (prefer black)
E501,
# E701 multiple statements on one line (colon) (prefer black, see https://github.com/psf/black/issues/4173)
E701,
# B010 Do not call setattr with a constant attribute value
B010,
# G200 Logging statement uses exception in arguments
@@ -38,4 +40,4 @@ per-file-ignores =
copyright-check = True
copyright-min-file-size = 1
copyright-regexp = Copyright \(C\) 2023 Mandiant, Inc. All Rights Reserved.
copyright-regexp = Copyright \(C\) \d{4} Mandiant, Inc. All Rights Reserved.

View File

@@ -1,11 +1,5 @@
[mypy]
[mypy-halo.*]
ignore_missing_imports = True
[mypy-tqdm.*]
ignore_missing_imports = True
[mypy-ruamel.*]
ignore_missing_imports = True
@@ -86,3 +80,6 @@ ignore_missing_imports = True
[mypy-netnode.*]
ignore_missing_imports = True
[mypy-ghidra.*]
ignore_missing_imports = True

View File

@@ -24,7 +24,7 @@ excludedimports = [
"pyqtwebengine",
# the above are imported by these viv modules.
# so really, we'd want to exclude these submodules of viv.
# but i dont think this works.
# but i don't think this works.
"vqt",
"vdb.qt",
"envi.qt",

View File

@@ -1,10 +1,18 @@
# -*- mode: python -*-
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
import os.path
import subprocess
import sys
import wcwidth
import capa.rules.cache
from pathlib import Path
# SPECPATH is a global variable which points to .spec file path
capa_dir = Path(SPECPATH).parent.parent
rules_dir = capa_dir / 'rules'
cache_dir = capa_dir / 'cache'
if not capa.rules.cache.generate_rule_cache(rules_dir, cache_dir):
sys.exit(-1)
a = Analysis(
# when invoking pyinstaller from the project root,
@@ -20,13 +28,6 @@ a = Analysis(
("../../rules", "rules"),
("../../sigs", "sigs"),
("../../cache", "cache"),
# capa.render.default uses tabulate that depends on wcwidth.
# it seems wcwidth uses a json file `version.json`
# and this doesn't get picked up by pyinstaller automatically.
# so we manually embed the wcwidth resources here.
#
# ref: https://stackoverflow.com/a/62278462/87207
(os.path.dirname(wcwidth.__file__), "wcwidth"),
],
# when invoking pyinstaller from the project root,
# this gets run from the project root.
@@ -39,11 +40,6 @@ a = Analysis(
"tkinter",
"_tkinter",
"Tkinter",
# tqdm provides renderers for ipython,
# however, this drags in a lot of dependencies.
# since we don't spawn a notebook, we can safely remove these.
"IPython",
"ipywidgets",
# these are pulled in by networkx
# but we don't need to compute the strongly connected components.
"numpy",
@@ -61,7 +57,10 @@ a = Analysis(
"qt5",
"pyqtwebengine",
"pyasn1",
# don't pull in Binary Ninja/IDA bindings that should
# only be installed locally.
"binaryninja",
"ida",
],
)
@@ -79,7 +78,7 @@ exe = EXE(
name="capa",
icon="logo.ico",
debug=False,
strip=None,
strip=False,
upx=True,
console=True,
)

8
.github/ruff.toml vendored
View File

@@ -1,16 +1,16 @@
# Enable the pycodestyle (`E`) and Pyflakes (`F`) rules by default.
# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or
# McCabe complexity (`C901`) by default.
select = ["E", "F"]
lint.select = ["E", "F"]
# Allow autofix for all enabled rules (when `--fix`) is provided.
fixable = ["ALL"]
unfixable = []
lint.fixable = ["ALL"]
lint.unfixable = []
# E402 module level import not at top of file
# E722 do not use bare 'except'
# E501 line too long
ignore = ["E402", "E722", "E501"]
lint.ignore = ["E402", "E722", "E501"]
line-length = 120

View File

@@ -3,6 +3,10 @@ name: build
on:
pull_request:
branches: [ master ]
paths-ignore:
- 'web/**'
- 'doc/**'
- '**.md'
release:
types: [edited, published]
@@ -11,7 +15,7 @@ permissions:
jobs:
build:
name: PyInstaller for ${{ matrix.os }}
name: PyInstaller for ${{ matrix.os }} / Py ${{ matrix.python_version }}
runs-on: ${{ matrix.os }}
strategy:
# set to false for debugging
@@ -22,46 +26,56 @@ jobs:
# use old linux so that the shared library versioning is more portable
artifact_name: capa
asset_name: linux
python_version: '3.10'
- os: ubuntu-20.04
artifact_name: capa
asset_name: linux-py312
python_version: '3.12'
- os: windows-2019
artifact_name: capa.exe
asset_name: windows
- os: macos-11
python_version: '3.10'
- os: macos-13
# use older macOS for assumed better portability
artifact_name: capa
asset_name: macos
python_version: '3.10'
steps:
- name: Checkout capa
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
submodules: true
# using Python 3.8 to support running across multiple operating systems including Windows 7
- name: Set up Python 3.8
uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 # v4.5.0
- name: Set up Python ${{ matrix.python_version }}
uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0
with:
python-version: 3.8
python-version: ${{ matrix.python_version }}
- if: matrix.os == 'ubuntu-20.04'
run: sudo apt-get install -y libyaml-dev
- name: Upgrade pip, setuptools
run: python -m pip install --upgrade pip setuptools
- name: Install capa with build requirements
run: pip install -e .[build]
- name: Cache the rule set
run: python ./scripts/cache-ruleset.py ./rules/ ./cache/
run: |
pip install -r requirements.txt
pip install -e .[build]
- name: Build standalone executable
run: pyinstaller --log-level DEBUG .github/pyinstaller/pyinstaller.spec
- name: Does it run (PE)?
run: dist/capa "tests/data/Practical Malware Analysis Lab 01-01.dll_"
run: dist/capa -d "tests/data/Practical Malware Analysis Lab 01-01.dll_"
- name: Does it run (Shellcode)?
run: dist/capa "tests/data/499c2a85f6e8142c3f48d4251c9c7cd6.raw32"
run: dist/capa -d "tests/data/499c2a85f6e8142c3f48d4251c9c7cd6.raw32"
- name: Does it run (ELF)?
run: dist/capa "tests/data/7351f8a40c5450557b24622417fc478d.elf_"
- uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
run: dist/capa -d "tests/data/7351f8a40c5450557b24622417fc478d.elf_"
- name: Does it run (CAPE)?
run: |
7z e "tests/data/dynamic/cape/v2.2/d46900384c78863420fb3e297d0a2f743cd2b6b3f7f82bf64059a168e07aceb7.json.gz"
dist/capa -d "d46900384c78863420fb3e297d0a2f743cd2b6b3f7f82bf64059a168e07aceb7.json"
- uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: ${{ matrix.asset_name }}
path: dist/${{ matrix.artifact_name }}
test_run:
name: Test run on ${{ matrix.os }}
name: Test run on ${{ matrix.os }} / ${{ matrix.asset_name }}
runs-on: ${{ matrix.os }}
needs: [build]
strategy:
@@ -71,12 +85,15 @@ jobs:
- os: ubuntu-22.04
artifact_name: capa
asset_name: linux
- os: ubuntu-22.04
artifact_name: capa
asset_name: linux-py312
- os: windows-2022
artifact_name: capa.exe
asset_name: windows
steps:
- name: Download ${{ matrix.asset_name }}
uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2
uses: actions/download-artifact@eaceaf801fd36c7dee90939fad912460b18a1ffe # v4.1.2
with:
name: ${{ matrix.asset_name }}
- name: Set executable flag
@@ -89,20 +106,22 @@ jobs:
# upload zipped binaries to Release page
if: github.event_name == 'release'
name: zip and upload ${{ matrix.asset_name }}
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
needs: [build]
strategy:
matrix:
include:
- asset_name: linux
artifact_name: capa
- asset_name: linux-py312
artifact_name: capa
- asset_name: windows
artifact_name: capa.exe
- asset_name: macos
artifact_name: capa
steps:
- name: Download ${{ matrix.asset_name }}
uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2
uses: actions/download-artifact@eaceaf801fd36c7dee90939fad912460b18a1ffe # v4.1.2
with:
name: ${{ matrix.asset_name }}
- name: Set executable flag

View File

@@ -7,19 +7,23 @@ on:
pull_request_target:
types: [opened, edited, synchronize]
permissions: read-all
permissions:
pull-requests: write
jobs:
check_changelog:
# no need to check for dependency updates via dependabot
if: github.actor != 'dependabot[bot]' && github.actor != 'dependabot-preview[bot]'
runs-on: ubuntu-20.04
# github.event.pull_request.user.login refers to PR author
if: |
github.event.pull_request.user.login != 'dependabot[bot]' &&
github.event.pull_request.user.login != 'dependabot-preview[bot]'
runs-on: ubuntu-latest
env:
NO_CHANGELOG: '[x] No CHANGELOG update needed'
steps:
- name: Get changed files
id: files
uses: Ana06/get-changed-files@e0c398b7065a8d84700c471b6afc4116d1ba4e96 # v2.2.0
uses: Ana06/get-changed-files@25f79e676e7ea1868813e21465014798211fad8c # v2.3.0
- name: check changelog updated
id: changelog_updated
env:
@@ -29,14 +33,14 @@ jobs:
echo $FILES | grep -qF 'CHANGELOG.md' || echo $PR_BODY | grep -qiF "$NO_CHANGELOG"
- name: Reject pull request if no CHANGELOG update
if: ${{ always() && steps.changelog_updated.outcome == 'failure' }}
uses: Ana06/automatic-pull-request-review@0cf4e8a17ba79344ed3fdd7fed6dd0311d08a9d4 # v0.1.0
uses: Ana06/automatic-pull-request-review@76aaf9b15b116a54e1da7a28a46f91fe089600bf # v0.2.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
event: REQUEST_CHANGES
body: "Please add bug fixes, new features, breaking changes and anything else you think is worthwhile mentioning to the `master (unreleased)` section of CHANGELOG.md. If no CHANGELOG update is needed add the following to the PR description: `${{ env.NO_CHANGELOG }}`"
allow_duplicate: false
- name: Dismiss previous review if CHANGELOG update
uses: Ana06/automatic-pull-request-review@0cf4e8a17ba79344ed3fdd7fed6dd0311d08a9d4 # v0.1.0
uses: Ana06/automatic-pull-request-review@76aaf9b15b116a54e1da7a28a46f91fe089600bf # v0.2.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
event: DISMISS

21
.github/workflows/pip-audit.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: PIP audit
on:
schedule:
- cron: '0 8 * * 1'
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
matrix:
python-version: ["3.11"]
steps:
- name: Check out repository code
uses: actions/checkout@v4
- uses: pypa/gh-action-pip-audit@v1.0.8
with:
inputs: .

View File

@@ -17,20 +17,21 @@ jobs:
permissions:
id-token: write
steps:
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Set up Python
uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 # v4.5.0
uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0
with:
python-version: '3.8'
python-version: '3.10'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -e .[build]
- name: build package
run: |
python -m build
- name: upload package artifacts
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
path: dist/*
- name: publish package

View File

@@ -32,12 +32,12 @@ jobs:
steps:
- name: "Checkout code"
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@99c53751e09b9529366343771cc321ec74e9bd3d # v2.0.6
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
with:
results_file: results.sarif
results_format: sarif
@@ -59,7 +59,7 @@ jobs:
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab.
- name: "Upload artifact"
uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # v3.1.0
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: SARIF file
path: results.sarif
@@ -67,6 +67,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@807578363a7869ca324a79039e6db9c843e0e100 # v2.1.27
uses: github/codeql-action/upload-sarif@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v3.24.6
with:
sarif_file: results.sarif

View File

@@ -9,10 +9,10 @@ permissions: read-all
jobs:
tag:
name: Tag capa rules
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
steps:
- name: Checkout capa-rules
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
repository: mandiant/capa-rules
token: ${{ secrets.CAPA_TOKEN }}
@@ -25,7 +25,7 @@ jobs:
git tag $name -m "https://github.com/mandiant/capa/releases/$name"
# TODO update branch name-major=${name%%.*}
- name: Push tag to capa-rules
uses: ad-m/github-push-action@0fafdd62b84042d49ec0cb92d9cac7f7ce4ec79e # master
uses: ad-m/github-push-action@d91a481090679876dfc4178fef17f286781251df # v0.8.0
with:
repository: mandiant/capa-rules
github_token: ${{ secrets.CAPA_TOKEN }}

View File

@@ -1,10 +1,22 @@
name: CI
# tests.yml workflow will run for all changes except:
# any file or directory under web/ or doc/
# any Markdown (.md) file anywhere in the repository
on:
push:
branches: [ master ]
paths-ignore:
- 'web/**'
- 'doc/**'
- '**.md'
pull_request:
branches: [ master ]
paths-ignore:
- 'web/**'
- 'doc/**'
- '**.md'
permissions: read-all
@@ -14,10 +26,10 @@ env:
jobs:
changelog_format:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- name: Checkout capa
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
# The sync GH action in capa-rules relies on a single '- *$' in the CHANGELOG file
- name: Ensure CHANGELOG has '- *$'
run: |
@@ -25,41 +37,47 @@ jobs:
if [ $number != 1 ]; then exit 1; fi
code_style:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- name: Checkout capa
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
# use latest available python to take advantage of best performance
- name: Set up Python 3.11
uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 # v4.5.0
- name: Set up Python 3.12
uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0
with:
python-version: "3.11"
python-version: "3.12"
- name: Install dependencies
run: pip install -e .[dev]
run: |
pip install -r requirements.txt
pip install -e .[dev,scripts]
- name: Lint with ruff
run: pre-commit run ruff
- name: Lint with isort
run: pre-commit run isort
run: pre-commit run isort --show-diff-on-failure
- name: Lint with black
run: pre-commit run black
run: pre-commit run black --show-diff-on-failure
- name: Lint with flake8
run: pre-commit run flake8
run: pre-commit run flake8 --hook-stage manual
- name: Check types with mypy
run: pre-commit run mypy
run: pre-commit run mypy --hook-stage manual
- name: Check imports against dependencies
run: pre-commit run deptry --hook-stage manual
rule_linter:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- name: Checkout capa with submodules
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
submodules: recursive
- name: Set up Python 3.11
uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 # v4.5.0
- name: Set up Python 3.12
uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0
with:
python-version: "3.11"
python-version: "3.12"
- name: Install capa
run: pip install -e .[dev]
run: |
pip install -r requirements.txt
pip install -e .[dev,scripts]
- name: Run rule linter
run: python scripts/lint.py rules/
@@ -70,31 +88,37 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-20.04, windows-2019, macos-11]
os: [ubuntu-20.04, windows-2019, macos-13]
# across all operating systems
python-version: ["3.8", "3.11"]
python-version: ["3.10", "3.11"]
include:
# on Ubuntu run these as well
- os: ubuntu-20.04
python-version: "3.8"
- os: ubuntu-20.04
python-version: "3.9"
- os: ubuntu-20.04
python-version: "3.10"
- os: ubuntu-20.04
python-version: "3.11"
- os: ubuntu-20.04
python-version: "3.12"
steps:
- name: Checkout capa with submodules
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
submodules: recursive
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 # v4.5.0
uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0
with:
python-version: ${{ matrix.python-version }}
- name: Install pyyaml
if: matrix.os == 'ubuntu-20.04'
run: sudo apt-get install -y libyaml-dev
- name: Install capa
run: pip install -e .[dev]
run: |
pip install -r requirements.txt
pip install -e .[dev,scripts]
- name: Run tests (fast)
# this set of tests runs about 80% of the cases in 20% of the time,
# and should catch most errors quickly.
run: pre-commit run pytest-fast --all-files --hook-stage manual
- name: Run tests
run: pytest -v tests/
@@ -102,22 +126,22 @@ jobs:
name: Binary Ninja tests for ${{ matrix.python-version }}
env:
BN_SERIAL: ${{ secrets.BN_SERIAL }}
runs-on: ubuntu-20.04
needs: [code_style, rule_linter]
runs-on: ubuntu-22.04
needs: [tests]
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.11"]
python-version: ["3.10", "3.11"]
steps:
- name: Checkout capa with submodules
# do only run if BN_SERIAL is available, have to do this in every step, see https://github.com/orgs/community/discussions/26726#discussioncomment-3253118
if: ${{ env.BN_SERIAL != 0 }}
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
submodules: recursive
- name: Set up Python ${{ matrix.python-version }}
if: ${{ env.BN_SERIAL != 0 }}
uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 # v4.5.0
uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0
with:
python-version: ${{ matrix.python-version }}
- name: Install pyyaml
@@ -125,7 +149,9 @@ jobs:
run: sudo apt-get install -y libyaml-dev
- name: Install capa
if: ${{ env.BN_SERIAL != 0 }}
run: pip install -e .[dev]
run: |
pip install -r requirements.txt
pip install -e .[dev,scripts]
- name: install Binary Ninja
if: ${{ env.BN_SERIAL != 0 }}
run: |
@@ -139,3 +165,57 @@ jobs:
env:
BN_LICENSE: ${{ secrets.BN_LICENSE }}
run: pytest -v tests/test_binja_features.py # explicitly refer to the binja tests for performance. other tests run above.
ghidra-tests:
name: Ghidra tests for ${{ matrix.python-version }}
runs-on: ubuntu-20.04
needs: [tests]
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11"]
java-version: ["17"]
ghidra-version: ["11.0.1"]
public-version: ["PUBLIC_20240130"] # for ghidra releases
ghidrathon-version: ["4.0.0"]
steps:
- name: Checkout capa with submodules
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
submodules: true
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0
with:
python-version: ${{ matrix.python-version }}
- name: Set up Java ${{ matrix.java-version }}
uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4.0.0
with:
distribution: 'temurin'
java-version: ${{ matrix.java-version }}
- name: Install Ghidra ${{ matrix.ghidra-version }}
run: |
mkdir ./.github/ghidra
wget "https://github.com/NationalSecurityAgency/ghidra/releases/download/Ghidra_${{ matrix.ghidra-version }}_build/ghidra_${{ matrix.ghidra-version }}_${{ matrix.public-version }}.zip" -O ./.github/ghidra/ghidra_${{ matrix.ghidra-version }}_PUBLIC.zip
unzip .github/ghidra/ghidra_${{ matrix.ghidra-version }}_PUBLIC.zip -d .github/ghidra/
- name: Install Ghidrathon
run : |
mkdir ./.github/ghidrathon
wget "https://github.com/mandiant/Ghidrathon/releases/download/v${{ matrix.ghidrathon-version }}/Ghidrathon-v${{ matrix.ghidrathon-version}}.zip" -O ./.github/ghidrathon/ghidrathon-v${{ matrix.ghidrathon-version }}.zip
unzip .github/ghidrathon/ghidrathon-v${{ matrix.ghidrathon-version }}.zip -d .github/ghidrathon/
python -m pip install -r .github/ghidrathon/requirements.txt
python .github/ghidrathon/ghidrathon_configure.py $(pwd)/.github/ghidra/ghidra_${{ matrix.ghidra-version }}_PUBLIC
unzip .github/ghidrathon/Ghidrathon-v${{ matrix.ghidrathon-version }}.zip -d .github/ghidra/ghidra_${{ matrix.ghidra-version }}_PUBLIC/Ghidra/Extensions
- name: Install pyyaml
run: sudo apt-get install -y libyaml-dev
- name: Install capa
run: |
pip install -r requirements.txt
pip install -e .[dev,scripts]
- name: Run tests
run: |
mkdir ./.github/ghidra/project
.github/ghidra/ghidra_${{ matrix.ghidra-version }}_PUBLIC/support/analyzeHeadless .github/ghidra/project ghidra_test -Import ./tests/data/mimikatz.exe_ -ScriptPath ./tests/ -PostScript test_ghidra_features.py > ../output.log
cat ../output.log
exit_code=$(cat ../output.log | grep exit | awk '{print $NF}')
exit $exit_code

134
.github/workflows/web-deploy.yml vendored Normal file
View File

@@ -0,0 +1,134 @@
name: deploy web to GitHub Pages
on:
push:
branches: [ master ]
paths:
- 'web/**'
# Allows to run this workflow manually from the Actions tab
workflow_dispatch:
# Sets the GITHUB_TOKEN permissions to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write
# Allow one concurrent deployment
concurrency:
group: 'pages'
cancel-in-progress: true
jobs:
build-landing-page:
name: Build landing page
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- uses: actions/upload-artifact@v4
with:
name: landing-page
path: './web/public'
build-explorer:
name: Build capa Explorer Web
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: 'recursive'
fetch-depth: 1
show-progress: true
- name: Set up Node
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
with:
node-version: 20
cache: 'npm'
cache-dependency-path: './web/explorer/package-lock.json'
- name: Install dependencies
run: npm ci
working-directory: ./web/explorer
- name: Generate release bundle
run: npm run build:bundle
working-directory: ./web/explorer
- name: Zip release bundle
run: zip -r public/capa-explorer-web.zip capa-explorer-web
working-directory: ./web/explorer
- name: Build
run: npm run build
working-directory: ./web/explorer
- uses: actions/upload-artifact@v4
with:
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, build-rules]
steps:
- uses: actions/download-artifact@v4
with:
name: landing-page
path: './public/'
- uses: actions/download-artifact@v4
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
uses: actions/upload-pages-artifact@v3
with:
path: './public'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4

103
.github/workflows/web-release.yml vendored Normal file
View File

@@ -0,0 +1,103 @@
name: create web release
on:
workflow_dispatch:
inputs:
version:
description: 'Version number for the release (x.x.x)'
required: true
type: string
jobs:
run-tests:
uses: ./.github/workflows/web-tests.yml
build-and-release:
needs: run-tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set release name
run: echo "RELEASE_NAME=capa-explorer-web-v${{ github.event.inputs.version }}-${GITHUB_SHA::7}" >> $GITHUB_ENV
- name: Check if release already exists
run: |
if ls web/explorer/releases/capa-explorer-web-v${{ github.event.inputs.version }}-* 1> /dev/null 2>&1; then
echo "::error:: A release with version ${{ github.event.inputs.version }} already exists"
exit 1
fi
- name: Set up Node.js
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
with:
node-version: 20
cache: 'npm'
cache-dependency-path: 'web/explorer/package-lock.json'
- name: Install dependencies
run: npm ci
working-directory: web/explorer
- name: Build offline bundle
run: npm run build:bundle
working-directory: web/explorer
- name: Compress bundle
run: zip -r ${{ env.RELEASE_NAME }}.zip capa-explorer-web
working-directory: web/explorer
- name: Create releases directory
run: mkdir -vp web/explorer/releases
- name: Move release to releases folder
run: mv web/explorer/${{ env.RELEASE_NAME }}.zip web/explorer/releases
- name: Compute release SHA256 hash
run: |
echo "RELEASE_SHA256=$(sha256sum web/explorer/releases/${{ env.RELEASE_NAME }}.zip | awk '{print $1}')" >> $GITHUB_ENV
- name: Update CHANGELOG.md
run: |
echo "## ${{ env.RELEASE_NAME }}" >> web/explorer/releases/CHANGELOG.md
echo "- Release Date: $(date -u '+%Y-%m-%d %H:%M:%S UTC')" >> web/explorer/releases/CHANGELOG.md
echo "- SHA256: ${{ env.RELEASE_SHA256 }}" >> web/explorer/releases/CHANGELOG.md
echo "" >> web/explorer/releases/CHANGELOG.md
cat web/explorer/releases/CHANGELOG.md
- name: Remove older releases
# keep only the latest 3 releases
run: ls -t capa-explorer-web-v*.zip | tail -n +4 | xargs -r rm --
working-directory: web/explorer/releases
- name: Stage release files
run: |
git config --local user.email "capa-dev@mandiant.com"
git config --local user.name "Capa Bot"
git add -f web/explorer/releases/${{ env.RELEASE_NAME }}.zip web/explorer/releases/CHANGELOG.md
git add -u web/explorer/releases/
- name: Create Pull Request
uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5
with:
token: ${{ secrets.GITHUB_TOKEN }}
title: "explorer web: add release v${{ github.event.inputs.version }}"
body: |
This PR adds a new capa Explorer Web release v${{ github.event.inputs.version }}.
Release details:
- Name: ${{ env.RELEASE_NAME }}
- SHA256: ${{ env.RELEASE_SHA256 }}
This release is generated by the [web release](https://github.com/mandiant/capa/actions/workflows/web-release.yml) workflow.
- [x] No CHANGELOG update needed
- [x] No new tests needed
- [x] No documentation update needed
commit-message: ":robot: explorer web: add release ${{ env.RELEASE_NAME }}"
branch: release/web-v${{ github.event.inputs.version }}
add-paths: web/explorer/releases/${{ env.RELEASE_NAME }}.zip
base: master
labels: webui
delete-branch: true
committer: Capa Bot <capa-dev@mandiant.com>
author: Capa Bot <capa-dev@mandiant.com>

43
.github/workflows/web-tests.yml vendored Normal file
View File

@@ -0,0 +1,43 @@
name: capa Explorer Web tests
on:
pull_request:
branches: [ master ]
paths:
- 'web/explorer/**'
workflow_call: # this allows the workflow to be called by other workflows
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: 'recursive'
fetch-depth: 1
show-progress: true
- name: Set up Node
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
with:
node-version: 20
cache: 'npm'
cache-dependency-path: 'web/explorer/package-lock.json'
- name: Install dependencies
run: npm ci
working-directory: web/explorer
- name: Lint
run: npm run lint
working-directory: web/explorer
- name: Format
run: npm run format:check
working-directory: web/explorer
- name: Run unit tests
run: npm run test
working-directory: web/explorer

2
.gitignore vendored
View File

@@ -126,3 +126,5 @@ Pipfile.lock
.github/binja/binaryninja
.github/binja/download_headless.py
.github/binja/BinaryNinja-headless.zip
justfile
data/

4
.gitmodules vendored
View File

@@ -1,6 +1,6 @@
[submodule "rules"]
path = rules
url = ../capa-rules.git
url = ../../mandiant/capa-rules.git
[submodule "tests/data"]
path = tests/data
url = ../capa-testfiles.git
url = ../../mandiant/capa-testfiles.git

25
.justfile Normal file
View File

@@ -0,0 +1,25 @@
@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:
-just isort
-just black
-just ruff
-just flake8
-just mypy
-just deptry

View File

@@ -25,7 +25,7 @@ repos:
hooks:
- id: isort
name: isort
stages: [commit, push]
stages: [pre-commit, pre-push, manual]
language: system
entry: isort
args:
@@ -38,6 +38,7 @@ repos:
- "capa/"
- "scripts/"
- "tests/"
- "web/rules/scripts/"
always_run: true
pass_filenames: false
@@ -45,7 +46,7 @@ repos:
hooks:
- id: black
name: black
stages: [commit, push]
stages: [pre-commit, pre-push, manual]
language: system
entry: black
args:
@@ -55,6 +56,7 @@ repos:
- "capa/"
- "scripts/"
- "tests/"
- "web/rules/scripts/"
always_run: true
pass_filenames: false
@@ -62,7 +64,7 @@ repos:
hooks:
- id: ruff
name: ruff
stages: [commit, push]
stages: [pre-commit, pre-push, manual]
language: system
entry: ruff
args:
@@ -72,6 +74,7 @@ repos:
- "capa/"
- "scripts/"
- "tests/"
- "web/rules/scripts/"
always_run: true
pass_filenames: false
@@ -79,17 +82,18 @@ repos:
hooks:
- id: flake8
name: flake8
stages: [commit, push]
stages: [pre-push, manual]
language: system
entry: flake8
args:
- "--config"
- ".github/flake8.ini"
- "--extend-exclude"
- "capa/render/proto/capa_pb2.py"
- "capa/render/proto/capa_pb2.py,capa/features/extractors/binexport2/binexport2_pb2.py"
- "capa/"
- "scripts/"
- "tests/"
- "web/rules/scripts/"
always_run: true
pass_filenames: false
@@ -97,7 +101,7 @@ repos:
hooks:
- id: mypy
name: mypy
stages: [commit, push]
stages: [pre-push, manual]
language: system
entry: mypy
args:
@@ -107,5 +111,35 @@ repos:
- "capa/"
- "scripts/"
- "tests/"
- "web/rules/scripts/"
always_run: true
pass_filenames: false
- repo: local
hooks:
- id: deptry
name: deptry
stages: [pre-push, manual]
language: system
entry: deptry .
always_run: true
pass_filenames: false
- repo: local
hooks:
- id: pytest-fast
name: pytest (fast)
stages: [manual]
language: system
entry: pytest
args:
- "tests/"
- "--ignore=tests/test_binja_features.py"
- "--ignore=tests/test_ghidra_features.py"
- "--ignore=tests/test_ida_features.py"
- "--ignore=tests/test_viv_features.py"
- "--ignore=tests/test_main.py"
- "--ignore=tests/test_scripts.py"
always_run: true
pass_filenames: false

File diff suppressed because it is too large Load Diff

8
CITATION.cff Normal file
View File

@@ -0,0 +1,8 @@
cff-version: 1.2.0
message: "If you use this software, please cite it as below."
authors:
- name: "The FLARE Team"
title: "capa, a tool to identify capabilities in programs and sandbox traces."
date-released: 2020-07-16
url: "https://github.com/mandiant/capa"

View File

@@ -187,7 +187,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright (C) 2023 Mandiant, Inc.
Copyright (C) 2020 Mandiant, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

194
README.md
View File

@@ -1,22 +1,40 @@
![capa](https://github.com/mandiant/capa/blob/master/.github/logo.png)
<br />
<div align="center">
<a href="https://mandiant.github.io/capa/" target="_blank">
<img src="https://github.com/mandiant/capa/blob/master/.github/logo.png">
</a>
<p align="center">
<a href="https://mandiant.github.io/capa/" target="_blank">Website</a>
|
<a href="https://github.com/mandiant/capa/releases/latest" target="_blank">Download</a>
|
<a href="https://mandiant.github.io/capa/explorer/" target="_blank">Web Interface</a>
</p>
<div align="center">
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/flare-capa)](https://pypi.org/project/flare-capa)
[![Last release](https://img.shields.io/github/v/release/mandiant/capa)](https://github.com/mandiant/capa/releases)
[![Number of rules](https://img.shields.io/badge/rules-823-blue.svg)](https://github.com/mandiant/capa-rules)
[![Number of rules](https://gist.githubusercontent.com/capa-bot/6d7960e911f48b3b74916df8988cf0f3/raw/rules_badge.svg)](https://github.com/mandiant/capa-rules)
[![CI status](https://github.com/mandiant/capa/workflows/CI/badge.svg)](https://github.com/mandiant/capa/actions?query=workflow%3ACI+event%3Apush+branch%3Amaster)
[![Downloads](https://img.shields.io/github/downloads/mandiant/capa/total)](https://github.com/mandiant/capa/releases)
[![License](https://img.shields.io/badge/license-Apache--2.0-green.svg)](LICENSE.txt)
</div>
</div>
---
capa detects capabilities in executable files.
You run it against a PE, ELF, .NET module, or shellcode file and it tells you what it thinks the program can do.
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.
Check out:
- the overview in our first [capa blog post](https://www.mandiant.com/resources/capa-automatically-identify-malware-capabilities)
- the major version 2.0 updates described in our [second blog post](https://www.mandiant.com/resources/capa-2-better-stronger-faster)
- the major version 3.0 (ELF support) described in the [third blog post](https://www.mandiant.com/resources/elfant-in-the-room-capa-v3)
- the major version 4.0 (.NET support) described in the [fourth blog post](https://www.mandiant.com/resources/blog/capa-v4-casting-wider-net)
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.
Below you find a list of [our capa blog posts with more details.](#blog-posts)
# example capa output
```
$ capa.exe suspicious.exe
@@ -71,16 +89,23 @@ 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.
For more information about how to use capa, see [doc/usage.md](https://github.com/mandiant/capa/blob/master/doc/usage.md).
# 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.
![capa Explorer Web screenshot](https://github.com/mandiant/capa/blob/master/doc/img/capa_web_explorer.png)
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
In the above sample output, we ran capa against an unknown binary (`suspicious.exe`),
and the tool reported that the program can send HTTP requests, decode data via XOR and Base64,
In the above sample output, we run capa against an unknown binary (`suspicious.exe`),
and the tool reports that the program can send HTTP requests, decode data via XOR and Base64,
install services, and spawn new processes.
Taken together, this makes us think that `suspicious.exe` could be a persistent backdoor.
Therefore, our next analysis step might be to run `suspicious.exe` in a sandbox and try to recover the command and control server.
## detailed results
By passing the `-vv` flag (for very verbose), capa reports exactly where it found evidence of these capabilities.
This is useful for at least two reasons:
@@ -125,6 +150,102 @@ function @ 0x4011C0
...
```
capa also supports dynamic capabilities detection for multiple sandboxes including:
* [CAPE](https://github.com/kevoreilly/CAPEv2) (supported report formats: `.json`, `.json_`, `.json.gz`)
* [DRAKVUF](https://github.com/CERT-Polska/drakvuf-sandbox/) (supported report formats: `.log`, `.log.gz`)
* [VMRay](https://www.vmray.com/) (supported report formats: analysis archive `.zip`)
To use this feature, submit your file to a supported sandbox and then download and run capa against the generated report file. This feature enables capa to match capabilities against dynamic and static features that the sandbox captured during execution.
Here's an example of running capa against a packed file, and then running capa against the CAPE report generated for the same packed file:
```yaml
$ capa 05be49819139a3fdcdbddbdefd298398779521f3d68daa25275cc77508e42310.exe
WARNING:capa.capabilities.common:--------------------------------------------------------------------------------
WARNING:capa.capabilities.common: This sample appears to be packed.
WARNING:capa.capabilities.common:
WARNING:capa.capabilities.common: Packed samples have often been obfuscated to hide their logic.
WARNING:capa.capabilities.common: capa cannot handle obfuscation well using static analysis. This means the results may be misleading or incomplete.
WARNING:capa.capabilities.common: If possible, you should try to unpack this input file before analyzing it with capa.
WARNING:capa.capabilities.common: Alternatively, run the sample in a supported sandbox and invoke capa against the report to obtain dynamic analysis results.
WARNING:capa.capabilities.common:
WARNING:capa.capabilities.common: Identified via rule: (internal) packer file limitation
WARNING:capa.capabilities.common:
WARNING:capa.capabilities.common: Use -v or -vv if you really want to see the capabilities identified by capa.
WARNING:capa.capabilities.common:--------------------------------------------------------------------------------
$ capa 05be49819139a3fdcdbddbdefd298398779521f3d68daa25275cc77508e42310.json
┍━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┑
│ ATT&CK Tactic │ ATT&CK Technique │
┝━━━━━━━━━━━━━━━━━━━━━━━━┿━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ CREDENTIAL ACCESS │ Credentials from Password Stores T1555 │
├────────────────────────┼────────────────────────────────────────────────────────────────────────────────────┤
│ DEFENSE EVASION │ File and Directory Permissions Modification T1222 │
│ │ Modify Registry T1112 │
│ │ Obfuscated Files or Information T1027 │
│ │ Virtualization/Sandbox Evasion::User Activity Based Checks T1497.002 │
├────────────────────────┼────────────────────────────────────────────────────────────────────────────────────┤
│ DISCOVERY │ Account Discovery T1087 │
│ │ Application Window Discovery T1010 │
│ │ File and Directory Discovery T1083 │
│ │ Query Registry T1012 │
│ │ System Information Discovery T1082 │
│ │ System Location Discovery::System Language Discovery T1614.001 │
│ │ System Owner/User Discovery T1033 │
├────────────────────────┼────────────────────────────────────────────────────────────────────────────────────┤
│ EXECUTION │ System Services::Service Execution T1569.002 │
├────────────────────────┼────────────────────────────────────────────────────────────────────────────────────┤
│ PERSISTENCE │ Boot or Logon Autostart Execution::Registry Run Keys / Startup Folder T1547.001 │
│ │ Boot or Logon Autostart Execution::Winlogon Helper DLL T1547.004 │
│ │ Create or Modify System Process::Windows Service T1543.003 │
┕━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┙
┍━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┑
│ Capability │ Namespace │
┝━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┿━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥
│ check for unmoving mouse cursor (3 matches) │ anti-analysis/anti-vm/vm-detection │
│ gather bitkinex information │ collection/file-managers │
│ gather classicftp information │ collection/file-managers │
│ gather filezilla information │ collection/file-managers │
│ gather total-commander information │ collection/file-managers │
│ gather ultrafxp information │ collection/file-managers │
│ resolve DNS (23 matches) │ communication/dns │
│ initialize Winsock library (7 matches) │ communication/socket │
│ act as TCP client (3 matches) │ communication/tcp/client │
│ create new key via CryptAcquireContext │ data-manipulation/encryption │
│ encrypt or decrypt via WinCrypt │ data-manipulation/encryption │
│ hash data via WinCrypt │ data-manipulation/hashing │
│ initialize hashing via WinCrypt │ data-manipulation/hashing │
│ hash data with MD5 │ data-manipulation/hashing/md5 │
│ generate random numbers via WinAPI │ data-manipulation/prng │
│ extract resource via kernel32 functions (2 matches) │ executable/resource │
│ interact with driver via control codes (2 matches) │ host-interaction/driver │
│ get Program Files directory (18 matches) │ host-interaction/file-system │
│ get common file path (575 matches) │ host-interaction/file-system │
│ create directory (2 matches) │ host-interaction/file-system/create │
│ delete file │ host-interaction/file-system/delete │
│ get file attributes (122 matches) │ host-interaction/file-system/meta │
│ set file attributes (8 matches) │ host-interaction/file-system/meta │
│ move file │ host-interaction/file-system/move │
│ find taskbar (3 matches) │ host-interaction/gui/taskbar/find │
│ get keyboard layout (12 matches) │ host-interaction/hardware/keyboard │
│ get disk size │ host-interaction/hardware/storage │
│ get hostname (4 matches) │ host-interaction/os/hostname │
│ allocate or change RWX memory (3 matches) │ host-interaction/process/inject │
│ query or enumerate registry key (3 matches) │ host-interaction/registry │
│ query or enumerate registry value (8 matches) │ host-interaction/registry │
│ delete registry key │ host-interaction/registry/delete │
│ start service │ host-interaction/service/start │
│ get session user name │ host-interaction/session │
│ persist via Run registry key │ persistence/registry/run │
│ persist via Winlogon Helper DLL registry key │ persistence/registry/winlogon-helper │
│ persist via Windows service (2 matches) │ persistence/service │
┕━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┙
```
# capa rules
capa uses a collection of rules to identify capabilities within a program.
These rules are easy to write, even for those new to reverse engineering.
By authoring rules, you can extend the capabilities that capa recognizes.
@@ -135,41 +256,54 @@ Here's an example rule used by capa:
```yaml
rule:
meta:
name: hash data with CRC32
namespace: data-manipulation/checksum/crc32
name: create TCP socket
namespace: communication/socket/tcp
authors:
- moritz.raabe@mandiant.com
scope: function
- william.ballenthin@mandiant.com
- joakim@intezer.com
- anushka.virgaonkar@mandiant.com
scopes:
static: basic block
dynamic: call
mbc:
- Data::Checksum::CRC32 [C0032.001]
- Communication::Socket Communication::Create TCP Socket [C0001.011]
examples:
- 2D3EDC218A90F03089CC01715A9F047F:0x403CBD
- 7D28CB106CB54876B2A5C111724A07CD:0x402350 # RtlComputeCrc32
- 7EFF498DE13CC734262F87E6B3EF38AB:0x100084A6
- Practical Malware Analysis Lab 01-01.dll_:0x10001010
features:
- or:
- and:
- mnemonic: shr
- number: 6 = IPPROTO_TCP
- number: 1 = SOCK_STREAM
- number: 2 = AF_INET
- or:
- number: 0xEDB88320
- bytes: 00 00 00 00 96 30 07 77 2C 61 0E EE BA 51 09 99 19 C4 6D 07 8F F4 6A 70 35 A5 63 E9 A3 95 64 9E = crc32_tab
- number: 8
- characteristic: nzxor
- and:
- number: 0x8320
- number: 0xEDB8
- characteristic: nzxor
- api: RtlComputeCrc32
- api: ws2_32.socket
- api: ws2_32.WSASocket
- api: socket
- property/read: System.Net.Sockets.TcpClient::Client
```
The [github.com/mandiant/capa-rules](https://github.com/mandiant/capa-rules) repository contains hundreds of standard library rules that are distributed with capa.
The [github.com/mandiant/capa-rules](https://github.com/mandiant/capa-rules) repository contains hundreds of standard rules that are distributed with capa.
Please learn to write rules and contribute new entries as you find interesting techniques in malware.
# IDA Pro plugin: capa explorer
If you use IDA Pro, then you can use the [capa explorer](https://github.com/mandiant/capa/tree/master/capa/ida/plugin) plugin.
capa explorer helps you identify interesting areas of a program and build new capa rules using features extracted directly from your IDA Pro database.
It also uses your local changes to the .idb to extract better features, such as when you rename a global variable that contains a dynamically resolved API address.
![capa + IDA Pro integration](https://github.com/mandiant/capa/blob/master/doc/img/explorer_expanded.png)
# Ghidra integration
If you use Ghidra, then you can use the [capa + Ghidra integration](/capa/ghidra/) to run capa's analysis directly on your Ghidra database and render the results in Ghidra's user interface.
<img src="https://github.com/mandiant/capa/assets/66766340/eeae33f4-99d4-42dc-a5e8-4c1b8c661492" width=300>
# blog posts
- [Dynamic capa: Exploring Executable Run-Time Behavior with the CAPE Sandbox](https://www.mandiant.com/resources/blog/dynamic-capa-executable-behavior-cape-sandbox)
- [capa v4: casting a wider .NET](https://www.mandiant.com/resources/blog/capa-v4-casting-wider-net) (.NET support)
- [ELFant in the Room capa v3](https://www.mandiant.com/resources/elfant-in-the-room-capa-v3) (ELF support)
- [capa 2.0: Better, Stronger, Faster](https://www.mandiant.com/resources/capa-2-better-stronger-faster)
- [capa: Automatically Identify Malware Capabilities](https://www.mandiant.com/resources/capa-automatically-identify-malware-capabilities)
# further information
## capa
- [Installation](https://github.com/mandiant/capa/blob/master/doc/installation.md)

View File

View File

@@ -0,0 +1,79 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2023 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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.
import logging
import itertools
import collections
from typing import Any
from capa.rules import Scope, RuleSet
from capa.engine import FeatureSet, MatchResults
from capa.features.address import NO_ADDRESS
from capa.features.extractors.base_extractor import FeatureExtractor, StaticFeatureExtractor, DynamicFeatureExtractor
logger = logging.getLogger(__name__)
def find_file_capabilities(ruleset: RuleSet, extractor: FeatureExtractor, function_features: FeatureSet):
file_features: FeatureSet = collections.defaultdict(set)
for feature, va in itertools.chain(extractor.extract_file_features(), extractor.extract_global_features()):
# not all file features may have virtual addresses.
# if not, then at least ensure the feature shows up in the index.
# the set of addresses will still be empty.
if va:
file_features[feature].add(va)
else:
if feature not in file_features:
file_features[feature] = set()
logger.debug("analyzed file and extracted %d features", len(file_features))
file_features.update(function_features)
_, matches = ruleset.match(Scope.FILE, file_features, NO_ADDRESS)
return matches, len(file_features)
def has_file_limitation(rules: RuleSet, capabilities: MatchResults, is_standalone=True) -> bool:
file_limitation_rules = list(filter(lambda r: r.is_file_limitation_rule(), rules.rules.values()))
for file_limitation_rule in file_limitation_rules:
if file_limitation_rule.name not in capabilities:
continue
logger.warning("-" * 80)
for line in file_limitation_rule.meta.get("description", "").split("\n"):
logger.warning(" %s", line)
logger.warning(" Identified via rule: %s", file_limitation_rule.name)
if is_standalone:
logger.warning(" ")
logger.warning(" Use -v or -vv if you really want to see the capabilities identified by capa.")
logger.warning("-" * 80)
# bail on first file limitation
return True
return False
def find_capabilities(
ruleset: RuleSet, extractor: FeatureExtractor, disable_progress=None, **kwargs
) -> tuple[MatchResults, Any]:
from capa.capabilities.static import find_static_capabilities
from capa.capabilities.dynamic import find_dynamic_capabilities
if isinstance(extractor, StaticFeatureExtractor):
# for the time being, extractors are either static or dynamic.
# Remove this assertion once that has changed
assert not isinstance(extractor, DynamicFeatureExtractor)
return find_static_capabilities(ruleset, extractor, disable_progress=disable_progress, **kwargs)
if isinstance(extractor, DynamicFeatureExtractor):
return find_dynamic_capabilities(ruleset, extractor, disable_progress=disable_progress, **kwargs)
raise ValueError(f"unexpected extractor type: {extractor.__class__.__name__}")

View File

@@ -0,0 +1,192 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2023 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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.
import logging
import itertools
import collections
from typing import Any
import capa.perf
import capa.features.freeze as frz
import capa.render.result_document as rdoc
from capa.rules import Scope, RuleSet
from capa.engine import FeatureSet, MatchResults
from capa.capabilities.common import find_file_capabilities
from capa.features.extractors.base_extractor import CallHandle, ThreadHandle, ProcessHandle, DynamicFeatureExtractor
logger = logging.getLogger(__name__)
def find_call_capabilities(
ruleset: RuleSet, extractor: DynamicFeatureExtractor, ph: ProcessHandle, th: ThreadHandle, ch: CallHandle
) -> tuple[FeatureSet, MatchResults]:
"""
find matches for the given rules for the given call.
returns: tuple containing (features for call, match results for call)
"""
# all features found for the call.
features: FeatureSet = collections.defaultdict(set)
for feature, addr in itertools.chain(
extractor.extract_call_features(ph, th, ch), extractor.extract_global_features()
):
features[feature].add(addr)
# matches found at this thread.
_, matches = ruleset.match(Scope.CALL, features, ch.address)
for rule_name, res in matches.items():
rule = ruleset[rule_name]
for addr, _ in res:
capa.engine.index_rule_matches(features, rule, [addr])
return features, matches
def find_thread_capabilities(
ruleset: RuleSet, extractor: DynamicFeatureExtractor, ph: ProcessHandle, th: ThreadHandle
) -> tuple[FeatureSet, MatchResults, MatchResults]:
"""
find matches for the given rules within the given thread.
returns: tuple containing (features for thread, match results for thread, match results for calls)
"""
# all features found within this thread,
# includes features found within calls.
features: FeatureSet = collections.defaultdict(set)
# matches found at the call scope.
# might be found at different calls, that's ok.
call_matches: MatchResults = collections.defaultdict(list)
for ch in extractor.get_calls(ph, th):
ifeatures, imatches = find_call_capabilities(ruleset, extractor, ph, th, ch)
for feature, vas in ifeatures.items():
features[feature].update(vas)
for rule_name, res in imatches.items():
call_matches[rule_name].extend(res)
for feature, va in itertools.chain(extractor.extract_thread_features(ph, th), extractor.extract_global_features()):
features[feature].add(va)
# matches found within this thread.
_, matches = ruleset.match(Scope.THREAD, features, th.address)
for rule_name, res in matches.items():
rule = ruleset[rule_name]
for va, _ in res:
capa.engine.index_rule_matches(features, rule, [va])
return features, matches, call_matches
def find_process_capabilities(
ruleset: RuleSet, extractor: DynamicFeatureExtractor, ph: ProcessHandle
) -> tuple[MatchResults, MatchResults, MatchResults, int]:
"""
find matches for the given rules within the given process.
returns: tuple containing (match results for process, match results for threads, match results for calls, number of features)
"""
# all features found within this process,
# includes features found within threads (and calls).
process_features: FeatureSet = collections.defaultdict(set)
# matches found at the basic threads.
# might be found at different threads, that's ok.
thread_matches: MatchResults = collections.defaultdict(list)
# matches found at the call scope.
# might be found at different calls, that's ok.
call_matches: MatchResults = collections.defaultdict(list)
for th in extractor.get_threads(ph):
features, tmatches, cmatches = find_thread_capabilities(ruleset, extractor, ph, th)
for feature, vas in features.items():
process_features[feature].update(vas)
for rule_name, res in tmatches.items():
thread_matches[rule_name].extend(res)
for rule_name, res in cmatches.items():
call_matches[rule_name].extend(res)
for feature, va in itertools.chain(extractor.extract_process_features(ph), extractor.extract_global_features()):
process_features[feature].add(va)
_, process_matches = ruleset.match(Scope.PROCESS, process_features, ph.address)
return process_matches, thread_matches, call_matches, len(process_features)
def find_dynamic_capabilities(
ruleset: RuleSet, extractor: DynamicFeatureExtractor, disable_progress=None
) -> tuple[MatchResults, Any]:
all_process_matches: MatchResults = collections.defaultdict(list)
all_thread_matches: MatchResults = collections.defaultdict(list)
all_call_matches: MatchResults = collections.defaultdict(list)
feature_counts = rdoc.DynamicFeatureCounts(file=0, processes=())
assert isinstance(extractor, DynamicFeatureExtractor)
processes: list[ProcessHandle] = list(extractor.get_processes())
n_processes: int = len(processes)
with capa.helpers.CapaProgressBar(
console=capa.helpers.log_console, transient=True, disable=disable_progress
) as pbar:
task = pbar.add_task("matching", total=n_processes, unit="processes")
for p in processes:
process_matches, thread_matches, call_matches, feature_count = find_process_capabilities(
ruleset, extractor, p
)
feature_counts.processes += (
rdoc.ProcessFeatureCount(address=frz.Address.from_capa(p.address), count=feature_count),
)
logger.debug("analyzed %s and extracted %d features", p.address, feature_count)
for rule_name, res in process_matches.items():
all_process_matches[rule_name].extend(res)
for rule_name, res in thread_matches.items():
all_thread_matches[rule_name].extend(res)
for rule_name, res in call_matches.items():
all_call_matches[rule_name].extend(res)
pbar.advance(task)
# collection of features that captures the rule matches within process and thread scopes.
# mapping from feature (matched rule) to set of addresses at which it matched.
process_and_lower_features: FeatureSet = collections.defaultdict(set)
for rule_name, results in itertools.chain(
all_process_matches.items(), all_thread_matches.items(), all_call_matches.items()
):
locations = {p[0] for p in results}
rule = ruleset[rule_name]
capa.engine.index_rule_matches(process_and_lower_features, rule, locations)
all_file_matches, feature_count = find_file_capabilities(ruleset, extractor, process_and_lower_features)
feature_counts.file = feature_count
matches = dict(
itertools.chain(
# each rule exists in exactly one scope,
# so there won't be any overlap among these following MatchResults,
# and we can merge the dictionaries naively.
all_thread_matches.items(),
all_process_matches.items(),
all_call_matches.items(),
all_file_matches.items(),
)
)
meta = {
"feature_counts": feature_counts,
}
return matches, meta

226
capa/capabilities/static.py Normal file
View File

@@ -0,0 +1,226 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2023 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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.
import time
import logging
import itertools
import collections
from typing import Any
import capa.perf
import capa.helpers
import capa.features.freeze as frz
import capa.render.result_document as rdoc
from capa.rules import Scope, RuleSet
from capa.engine import FeatureSet, MatchResults
from capa.capabilities.common import find_file_capabilities
from capa.features.extractors.base_extractor import BBHandle, InsnHandle, FunctionHandle, StaticFeatureExtractor
logger = logging.getLogger(__name__)
def find_instruction_capabilities(
ruleset: RuleSet, extractor: StaticFeatureExtractor, f: FunctionHandle, bb: BBHandle, insn: InsnHandle
) -> tuple[FeatureSet, MatchResults]:
"""
find matches for the given rules for the given instruction.
returns: tuple containing (features for instruction, match results for instruction)
"""
# all features found for the instruction.
features: FeatureSet = collections.defaultdict(set)
for feature, addr in itertools.chain(
extractor.extract_insn_features(f, bb, insn), extractor.extract_global_features()
):
features[feature].add(addr)
# matches found at this instruction.
_, matches = ruleset.match(Scope.INSTRUCTION, features, insn.address)
for rule_name, res in matches.items():
rule = ruleset[rule_name]
for addr, _ in res:
capa.engine.index_rule_matches(features, rule, [addr])
return features, matches
def find_basic_block_capabilities(
ruleset: RuleSet, extractor: StaticFeatureExtractor, f: FunctionHandle, bb: BBHandle
) -> tuple[FeatureSet, MatchResults, MatchResults]:
"""
find matches for the given rules within the given basic block.
returns: tuple containing (features for basic block, match results for basic block, match results for instructions)
"""
# all features found within this basic block,
# includes features found within instructions.
features: FeatureSet = collections.defaultdict(set)
# matches found at the instruction scope.
# might be found at different instructions, that's ok.
insn_matches: MatchResults = collections.defaultdict(list)
for insn in extractor.get_instructions(f, bb):
ifeatures, imatches = find_instruction_capabilities(ruleset, extractor, f, bb, insn)
for feature, vas in ifeatures.items():
features[feature].update(vas)
for rule_name, res in imatches.items():
insn_matches[rule_name].extend(res)
for feature, va in itertools.chain(
extractor.extract_basic_block_features(f, bb), extractor.extract_global_features()
):
features[feature].add(va)
# matches found within this basic block.
_, matches = ruleset.match(Scope.BASIC_BLOCK, features, bb.address)
for rule_name, res in matches.items():
rule = ruleset[rule_name]
for va, _ in res:
capa.engine.index_rule_matches(features, rule, [va])
return features, matches, insn_matches
def find_code_capabilities(
ruleset: RuleSet, extractor: StaticFeatureExtractor, fh: FunctionHandle
) -> tuple[MatchResults, MatchResults, MatchResults, int]:
"""
find matches for the given rules within the given function.
returns: tuple containing (match results for function, match results for basic blocks, match results for instructions, number of features)
"""
# all features found within this function,
# includes features found within basic blocks (and instructions).
function_features: FeatureSet = collections.defaultdict(set)
# matches found at the basic block scope.
# might be found at different basic blocks, that's ok.
bb_matches: MatchResults = collections.defaultdict(list)
# matches found at the instruction scope.
# might be found at different instructions, that's ok.
insn_matches: MatchResults = collections.defaultdict(list)
for bb in extractor.get_basic_blocks(fh):
features, bmatches, imatches = find_basic_block_capabilities(ruleset, extractor, fh, bb)
for feature, vas in features.items():
function_features[feature].update(vas)
for rule_name, res in bmatches.items():
bb_matches[rule_name].extend(res)
for rule_name, res in imatches.items():
insn_matches[rule_name].extend(res)
for feature, va in itertools.chain(extractor.extract_function_features(fh), extractor.extract_global_features()):
function_features[feature].add(va)
_, function_matches = ruleset.match(Scope.FUNCTION, function_features, fh.address)
return function_matches, bb_matches, insn_matches, len(function_features)
def find_static_capabilities(
ruleset: RuleSet, extractor: StaticFeatureExtractor, disable_progress=None
) -> tuple[MatchResults, Any]:
all_function_matches: MatchResults = collections.defaultdict(list)
all_bb_matches: MatchResults = collections.defaultdict(list)
all_insn_matches: MatchResults = collections.defaultdict(list)
feature_counts = rdoc.StaticFeatureCounts(file=0, functions=())
library_functions: tuple[rdoc.LibraryFunction, ...] = ()
assert isinstance(extractor, StaticFeatureExtractor)
functions: list[FunctionHandle] = list(extractor.get_functions())
n_funcs: int = len(functions)
n_libs: int = 0
percentage: float = 0
with capa.helpers.CapaProgressBar(
console=capa.helpers.log_console, transient=True, disable=disable_progress
) as pbar:
task = pbar.add_task(
"matching", total=n_funcs, unit="functions", postfix=f"skipped {n_libs} library functions, {percentage}%"
)
for f in functions:
t0 = time.time()
if extractor.is_library_function(f.address):
function_name = extractor.get_function_name(f.address)
logger.debug("skipping library function 0x%x (%s)", f.address, function_name)
library_functions += (
rdoc.LibraryFunction(address=frz.Address.from_capa(f.address), name=function_name),
)
n_libs = len(library_functions)
percentage = round(100 * (n_libs / n_funcs))
pbar.update(task, postfix=f"skipped {n_libs} library functions, {percentage}%")
pbar.advance(task)
continue
function_matches, bb_matches, insn_matches, feature_count = find_code_capabilities(ruleset, extractor, f)
feature_counts.functions += (
rdoc.FunctionFeatureCount(address=frz.Address.from_capa(f.address), count=feature_count),
)
t1 = time.time()
match_count = 0
for name, matches_ in itertools.chain(function_matches.items(), bb_matches.items(), insn_matches.items()):
if not ruleset.rules[name].is_subscope_rule():
match_count += len(matches_)
logger.debug(
"analyzed function 0x%x and extracted %d features, %d matches in %0.02fs",
f.address,
feature_count,
match_count,
t1 - t0,
)
for rule_name, res in function_matches.items():
all_function_matches[rule_name].extend(res)
for rule_name, res in bb_matches.items():
all_bb_matches[rule_name].extend(res)
for rule_name, res in insn_matches.items():
all_insn_matches[rule_name].extend(res)
pbar.advance(task)
# collection of features that captures the rule matches within function, BB, and instruction scopes.
# mapping from feature (matched rule) to set of addresses at which it matched.
function_and_lower_features: FeatureSet = collections.defaultdict(set)
for rule_name, results in itertools.chain(
all_function_matches.items(), all_bb_matches.items(), all_insn_matches.items()
):
locations = {p[0] for p in results}
rule = ruleset[rule_name]
capa.engine.index_rule_matches(function_and_lower_features, rule, locations)
all_file_matches, feature_count = find_file_capabilities(ruleset, extractor, function_and_lower_features)
feature_counts.file = feature_count
matches: MatchResults = dict(
itertools.chain(
# each rule exists in exactly one scope,
# so there won't be any overlap among these following MatchResults,
# and we can merge the dictionaries naively.
all_insn_matches.items(),
all_bb_matches.items(),
all_function_matches.items(),
all_file_matches.items(),
)
)
meta = {
"feature_counts": feature_counts,
"library_functions": library_functions,
}
return matches, meta

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2023 Mandiant, Inc. All Rights Reserved.
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
@@ -8,7 +8,7 @@
import copy
import collections
from typing import TYPE_CHECKING, Set, Dict, List, Tuple, Union, Mapping, Iterable, Iterator
from typing import TYPE_CHECKING, Union, Mapping, Iterable, Iterator
import capa.perf
import capa.features.common
@@ -27,7 +27,7 @@ if TYPE_CHECKING:
# to collect the locations of a feature, do: `features[Number(0x10)]`
#
# aliased here so that the type can be documented and xref'd.
FeatureSet = Dict[Feature, Set[Address]]
FeatureSet = dict[Feature, set[Address]]
class Statement:
@@ -94,7 +94,7 @@ class And(Statement):
match if all of the children evaluate to True.
the order of evaluation is dictated by the property
`And.children` (type: List[Statement|Feature]).
`And.children` (type: list[Statement|Feature]).
a query optimizer may safely manipulate the order of these children.
"""
@@ -102,14 +102,14 @@ class And(Statement):
super().__init__(description=description)
self.children = children
def evaluate(self, ctx, short_circuit=True):
def evaluate(self, features: FeatureSet, short_circuit=True):
capa.perf.counters["evaluate.feature"] += 1
capa.perf.counters["evaluate.feature.and"] += 1
if short_circuit:
results = []
for child in self.children:
result = child.evaluate(ctx, short_circuit=short_circuit)
result = child.evaluate(features, short_circuit=short_circuit)
results.append(result)
if not result:
# short circuit
@@ -117,7 +117,7 @@ class And(Statement):
return Result(True, self, results)
else:
results = [child.evaluate(ctx, short_circuit=short_circuit) for child in self.children]
results = [child.evaluate(features, short_circuit=short_circuit) for child in self.children]
success = all(results)
return Result(success, self, results)
@@ -127,7 +127,7 @@ class Or(Statement):
match if any of the children evaluate to True.
the order of evaluation is dictated by the property
`Or.children` (type: List[Statement|Feature]).
`Or.children` (type: list[Statement|Feature]).
a query optimizer may safely manipulate the order of these children.
"""
@@ -135,14 +135,14 @@ class Or(Statement):
super().__init__(description=description)
self.children = children
def evaluate(self, ctx, short_circuit=True):
def evaluate(self, features: FeatureSet, short_circuit=True):
capa.perf.counters["evaluate.feature"] += 1
capa.perf.counters["evaluate.feature.or"] += 1
if short_circuit:
results = []
for child in self.children:
result = child.evaluate(ctx, short_circuit=short_circuit)
result = child.evaluate(features, short_circuit=short_circuit)
results.append(result)
if result:
# short circuit as soon as we hit one match
@@ -150,7 +150,7 @@ class Or(Statement):
return Result(False, self, results)
else:
results = [child.evaluate(ctx, short_circuit=short_circuit) for child in self.children]
results = [child.evaluate(features, short_circuit=short_circuit) for child in self.children]
success = any(results)
return Result(success, self, results)
@@ -162,11 +162,11 @@ class Not(Statement):
super().__init__(description=description)
self.child = child
def evaluate(self, ctx, short_circuit=True):
def evaluate(self, features: FeatureSet, short_circuit=True):
capa.perf.counters["evaluate.feature"] += 1
capa.perf.counters["evaluate.feature.not"] += 1
results = [self.child.evaluate(ctx, short_circuit=short_circuit)]
results = [self.child.evaluate(features, short_circuit=short_circuit)]
success = not results[0]
return Result(success, self, results)
@@ -176,7 +176,7 @@ class Some(Statement):
match if at least N of the children evaluate to True.
the order of evaluation is dictated by the property
`Some.children` (type: List[Statement|Feature]).
`Some.children` (type: list[Statement|Feature]).
a query optimizer may safely manipulate the order of these children.
"""
@@ -185,7 +185,7 @@ class Some(Statement):
self.count = count
self.children = children
def evaluate(self, ctx, short_circuit=True):
def evaluate(self, features: FeatureSet, short_circuit=True):
capa.perf.counters["evaluate.feature"] += 1
capa.perf.counters["evaluate.feature.some"] += 1
@@ -193,7 +193,7 @@ class Some(Statement):
results = []
satisfied_children_count = 0
for child in self.children:
result = child.evaluate(ctx, short_circuit=short_circuit)
result = child.evaluate(features, short_circuit=short_circuit)
results.append(result)
if result:
satisfied_children_count += 1
@@ -204,7 +204,7 @@ class Some(Statement):
return Result(False, self, results)
else:
results = [child.evaluate(ctx, short_circuit=short_circuit) for child in self.children]
results = [child.evaluate(features, short_circuit=short_circuit) for child in self.children]
# note that here we cast the child result as a bool
# because we've overridden `__bool__` above.
#
@@ -214,7 +214,7 @@ class Some(Statement):
class Range(Statement):
"""match if the child is contained in the ctx set with a count in the given range."""
"""match if the child is contained in the feature set with a count in the given range."""
def __init__(self, child, min=None, max=None, description=None):
super().__init__(description=description)
@@ -222,15 +222,15 @@ class Range(Statement):
self.min = min if min is not None else 0
self.max = max if max is not None else (1 << 64 - 1)
def evaluate(self, ctx, **kwargs):
def evaluate(self, features: FeatureSet, short_circuit=True):
capa.perf.counters["evaluate.feature"] += 1
capa.perf.counters["evaluate.feature.range"] += 1
count = len(ctx.get(self.child, []))
count = len(features.get(self.child, []))
if self.min == 0 and count == 0:
return Result(True, self, [])
return Result(self.min <= count <= self.max, self, [], locations=ctx.get(self.child))
return Result(self.min <= count <= self.max, self, [], locations=features.get(self.child))
def __str__(self):
if self.max == (1 << 64 - 1):
@@ -250,7 +250,7 @@ class Subscope(Statement):
self.scope = scope
self.child = child
def evaluate(self, ctx, **kwargs):
def evaluate(self, features: FeatureSet, short_circuit=True):
raise ValueError("cannot evaluate a subscope directly!")
@@ -267,7 +267,15 @@ class Subscope(Statement):
# inspect(match_details)
#
# aliased here so that the type can be documented and xref'd.
MatchResults = Mapping[str, List[Tuple[Address, Result]]]
MatchResults = Mapping[str, list[tuple[Address, Result]]]
def get_rule_namespaces(rule: "capa.rules.Rule") -> Iterator[str]:
namespace = rule.meta.get("namespace")
if namespace:
while namespace:
yield namespace
namespace, _, _ = namespace.rpartition("/")
def index_rule_matches(features: FeatureSet, rule: "capa.rules.Rule", locations: Iterable[Address]):
@@ -280,14 +288,11 @@ def index_rule_matches(features: FeatureSet, rule: "capa.rules.Rule", locations:
updates `features` in-place. doesn't modify the remaining arguments.
"""
features[capa.features.common.MatchedRule(rule.name)].update(locations)
namespace = rule.meta.get("namespace")
if namespace:
while namespace:
features[capa.features.common.MatchedRule(namespace)].update(locations)
namespace, _, _ = namespace.rpartition("/")
for namespace in get_rule_namespaces(rule):
features[capa.features.common.MatchedRule(namespace)].update(locations)
def match(rules: List["capa.rules.Rule"], features: FeatureSet, addr: Address) -> Tuple[FeatureSet, MatchResults]:
def match(rules: list["capa.rules.Rule"], features: FeatureSet, addr: Address) -> tuple[FeatureSet, MatchResults]:
"""
match the given rules against the given features,
returning an updated set of features and the matches.
@@ -304,7 +309,7 @@ def match(rules: List["capa.rules.Rule"], features: FeatureSet, addr: Address) -
other strategies can be imagined that match differently; implement these elsewhere.
specifically, this routine does "top down" matching of the given rules against the feature set.
"""
results = collections.defaultdict(list) # type: MatchResults
results: MatchResults = collections.defaultdict(list)
# copy features so that we can modify it
# without affecting the caller (keep this function pure)

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2023 Mandiant, Inc. All Rights Reserved.
# Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
@@ -19,3 +19,19 @@ class UnsupportedArchError(ValueError):
class UnsupportedOSError(ValueError):
pass
class EmptyReportError(ValueError):
pass
class InvalidArgument(ValueError):
pass
class NonExistantFunctionError(ValueError):
pass
class NonExistantProcessError(ValueError):
pass

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2023 Mandiant, Inc. All Rights Reserved.
# Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
@@ -10,8 +10,7 @@ import abc
class Address(abc.ABC):
@abc.abstractmethod
def __eq__(self, other):
...
def __eq__(self, other): ...
@abc.abstractmethod
def __lt__(self, other):
@@ -43,6 +42,79 @@ class AbsoluteVirtualAddress(int, Address):
return int.__hash__(self)
class ProcessAddress(Address):
"""an address of a process in a dynamic execution trace"""
def __init__(self, pid: int, ppid: int = 0):
assert ppid >= 0
assert pid > 0
self.ppid = ppid
self.pid = pid
def __repr__(self):
return "process(%s%s)" % (
f"ppid: {self.ppid}, " if self.ppid > 0 else "",
f"pid: {self.pid}",
)
def __hash__(self):
return hash((self.ppid, self.pid))
def __eq__(self, other):
assert isinstance(other, ProcessAddress)
return (self.ppid, self.pid) == (other.ppid, other.pid)
def __lt__(self, other):
assert isinstance(other, ProcessAddress)
return (self.ppid, self.pid) < (other.ppid, other.pid)
class ThreadAddress(Address):
"""addresses a thread in a dynamic execution trace"""
def __init__(self, process: ProcessAddress, tid: int):
assert tid >= 0
self.process = process
self.tid = tid
def __repr__(self):
return f"{self.process}, thread(tid: {self.tid})"
def __hash__(self):
return hash((self.process, self.tid))
def __eq__(self, other):
assert isinstance(other, ThreadAddress)
return (self.process, self.tid) == (other.process, other.tid)
def __lt__(self, other):
assert isinstance(other, ThreadAddress)
return (self.process, self.tid) < (other.process, other.tid)
class DynamicCallAddress(Address):
"""addresses a call in a dynamic execution trace"""
def __init__(self, thread: ThreadAddress, id: int):
assert id >= 0
self.thread = thread
self.id = id
def __repr__(self):
return f"{self.thread}, call(id: {self.id})"
def __hash__(self):
return hash((self.thread, self.id))
def __eq__(self, other):
assert isinstance(other, DynamicCallAddress)
return (self.thread, self.id) == (other.thread, other.id)
def __lt__(self, other):
assert isinstance(other, DynamicCallAddress)
return (self.thread, self.id) < (other.thread, other.id)
class RelativeVirtualAddress(int, Address):
"""a memory address relative to a base address"""

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2023 Mandiant, Inc. All Rights Reserved.
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt

View File

@@ -0,0 +1,35 @@
# Copyright (C) 2024 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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.
from enum import Enum
from capa.helpers import assert_never
class ComType(Enum):
CLASS = "class"
INTERFACE = "interface"
COM_PREFIXES = {
ComType.CLASS: "CLSID_",
ComType.INTERFACE: "IID_",
}
def load_com_database(com_type: ComType) -> dict[str, list[str]]:
# lazy load these python files since they are so large.
# that is, don't load them unless a COM feature is being handled.
import capa.features.com.classes
import capa.features.com.interfaces
if com_type == ComType.CLASS:
return capa.features.com.classes.COM_CLASSES
elif com_type == ComType.INTERFACE:
return capa.features.com.interfaces.COM_INTERFACES
else:
assert_never(com_type)

3695
capa/features/com/classes.py Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2023 Mandiant, Inc. All Rights Reserved.
# Copyright (C) 2021 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
@@ -9,10 +9,9 @@
import re
import abc
import codecs
import typing
import logging
import collections
from typing import TYPE_CHECKING, Set, Dict, List, Union, Optional
from typing import TYPE_CHECKING, Union, Optional
if TYPE_CHECKING:
# circular import, otherwise
@@ -79,8 +78,8 @@ class Result:
self,
success: bool,
statement: Union["capa.engine.Statement", "Feature"],
children: List["Result"],
locations: Optional[Set[Address]] = None,
children: list["Result"],
locations: Optional[set[Address]] = None,
):
super().__init__()
self.success = success
@@ -128,7 +127,7 @@ class Feature(abc.ABC): # noqa: B024
def __lt__(self, other):
# implementing sorting by serializing to JSON is a huge hack.
# its slow, inelegant, and probably doesn't work intuitively;
# it's slow, inelegant, and probably doesn't work intuitively;
# however, we only use it for deterministic output, so it's good enough for now.
# circular import
@@ -136,8 +135,8 @@ class Feature(abc.ABC): # noqa: B024
import capa.features.freeze.features
return (
capa.features.freeze.features.feature_from_capa(self).json()
< capa.features.freeze.features.feature_from_capa(other).json()
capa.features.freeze.features.feature_from_capa(self).model_dump_json()
< capa.features.freeze.features.feature_from_capa(other).model_dump_json()
)
def get_name_str(self) -> str:
@@ -166,10 +165,10 @@ class Feature(abc.ABC): # noqa: B024
def __repr__(self):
return str(self)
def evaluate(self, ctx: Dict["Feature", Set[Address]], **kwargs) -> Result:
def evaluate(self, features: "capa.engine.FeatureSet", short_circuit=True) -> Result:
capa.perf.counters["evaluate.feature"] += 1
capa.perf.counters["evaluate.feature." + self.name] += 1
return Result(self in ctx, self, [], locations=ctx.get(self, set()))
return Result(self in features, self, [], locations=features.get(self, set()))
class MatchedRule(Feature):
@@ -207,16 +206,16 @@ class Substring(String):
super().__init__(value, description=description)
self.value = value
def evaluate(self, ctx, short_circuit=True):
def evaluate(self, features: "capa.engine.FeatureSet", short_circuit=True):
capa.perf.counters["evaluate.feature"] += 1
capa.perf.counters["evaluate.feature.substring"] += 1
# mapping from string value to list of locations.
# will unique the locations later on.
matches: typing.DefaultDict[str, Set[Address]] = collections.defaultdict(set)
matches: collections.defaultdict[str, set[Address]] = collections.defaultdict(set)
assert isinstance(self.value, str)
for feature, locations in ctx.items():
for feature, locations in features.items():
if not isinstance(feature, (String,)):
continue
@@ -227,7 +226,7 @@ class Substring(String):
if self.value in feature.value:
matches[feature.value].update(locations)
if short_circuit:
# we found one matching string, thats sufficient to match.
# we found one matching string, that's sufficient to match.
# don't collect other matching strings in this mode.
break
@@ -261,7 +260,7 @@ class _MatchedSubstring(Substring):
note: this type should only ever be constructed by `Substring.evaluate()`. it is not part of the public API.
"""
def __init__(self, substring: Substring, matches: Dict[str, Set[Address]]):
def __init__(self, substring: Substring, matches: dict[str, set[Address]]):
"""
args:
substring: the substring feature that matches.
@@ -299,15 +298,15 @@ class Regex(String):
f"invalid regular expression: {value} it should use Python syntax, try it at https://pythex.org"
) from exc
def evaluate(self, ctx, short_circuit=True):
def evaluate(self, features: "capa.engine.FeatureSet", short_circuit=True):
capa.perf.counters["evaluate.feature"] += 1
capa.perf.counters["evaluate.feature.regex"] += 1
# mapping from string value to list of locations.
# will unique the locations later on.
matches: typing.DefaultDict[str, Set[Address]] = collections.defaultdict(set)
matches: collections.defaultdict[str, set[Address]] = collections.defaultdict(set)
for feature, locations in ctx.items():
for feature, locations in features.items():
if not isinstance(feature, (String,)):
continue
@@ -322,7 +321,7 @@ class Regex(String):
if self.re.search(feature.value):
matches[feature.value].update(locations)
if short_circuit:
# we found one matching string, thats sufficient to match.
# we found one matching string, that's sufficient to match.
# don't collect other matching strings in this mode.
break
@@ -353,7 +352,7 @@ class _MatchedRegex(Regex):
note: this type should only ever be constructed by `Regex.evaluate()`. it is not part of the public API.
"""
def __init__(self, regex: Regex, matches: Dict[str, Set[Address]]):
def __init__(self, regex: Regex, matches: dict[str, set[Address]]):
"""
args:
regex: the regex feature that matches.
@@ -384,12 +383,14 @@ class Bytes(Feature):
super().__init__(value, description=description)
self.value = value
def evaluate(self, ctx, **kwargs):
def evaluate(self, features: "capa.engine.FeatureSet", short_circuit=True):
assert isinstance(self.value, bytes)
capa.perf.counters["evaluate.feature"] += 1
capa.perf.counters["evaluate.feature.bytes"] += 1
capa.perf.counters["evaluate.feature.bytes." + str(len(self.value))] += 1
assert isinstance(self.value, bytes)
for feature, locations in ctx.items():
for feature, locations in features.items():
if not isinstance(feature, (Bytes,)):
continue
@@ -407,9 +408,10 @@ class Bytes(Feature):
# other candidates here: https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#machine-types
ARCH_I386 = "i386"
ARCH_AMD64 = "amd64"
ARCH_AARCH64 = "aarch64"
# dotnet
ARCH_ANY = "any"
VALID_ARCH = (ARCH_I386, ARCH_AMD64, ARCH_ANY)
VALID_ARCH = (ARCH_I386, ARCH_AMD64, ARCH_AARCH64, ARCH_ANY)
class Arch(Feature):
@@ -421,10 +423,11 @@ class Arch(Feature):
OS_WINDOWS = "windows"
OS_LINUX = "linux"
OS_MACOS = "macos"
OS_ANDROID = "android"
# dotnet
OS_ANY = "any"
VALID_OS = {os.value for os in capa.features.extractors.elf.OS}
VALID_OS.update({OS_WINDOWS, OS_LINUX, OS_MACOS, OS_ANY})
VALID_OS.update({OS_WINDOWS, OS_LINUX, OS_MACOS, OS_ANY, OS_ANDROID})
# internal only, not to be used in rules
OS_AUTO = "auto"
@@ -434,11 +437,11 @@ class OS(Feature):
super().__init__(value, description=description)
self.name = "os"
def evaluate(self, ctx, **kwargs):
def evaluate(self, features: "capa.engine.FeatureSet", short_circuit=True):
capa.perf.counters["evaluate.feature"] += 1
capa.perf.counters["evaluate.feature." + self.name] += 1
for feature, locations in ctx.items():
for feature, locations in features.items():
if not isinstance(feature, (OS,)):
continue
@@ -457,8 +460,31 @@ VALID_FORMAT = (FORMAT_PE, FORMAT_ELF, FORMAT_DOTNET)
FORMAT_AUTO = "auto"
FORMAT_SC32 = "sc32"
FORMAT_SC64 = "sc64"
FORMAT_CAPE = "cape"
FORMAT_DRAKVUF = "drakvuf"
FORMAT_VMRAY = "vmray"
FORMAT_BINEXPORT2 = "binexport2"
FORMAT_FREEZE = "freeze"
FORMAT_RESULT = "result"
FORMAT_BINJA_DB = "binja_database"
STATIC_FORMATS = {
FORMAT_SC32,
FORMAT_SC64,
FORMAT_PE,
FORMAT_ELF,
FORMAT_DOTNET,
FORMAT_FREEZE,
FORMAT_RESULT,
FORMAT_BINEXPORT2,
FORMAT_BINJA_DB,
}
DYNAMIC_FORMATS = {
FORMAT_CAPE,
FORMAT_DRAKVUF,
FORMAT_VMRAY,
FORMAT_FREEZE,
FORMAT_RESULT,
}
FORMAT_UNKNOWN = "unknown"
@@ -471,6 +497,6 @@ class Format(Feature):
def is_global_feature(feature):
"""
is this a feature that is extracted at every scope?
today, these are OS and arch features.
today, these are OS, arch, and format features.
"""
return isinstance(feature, (OS, Arch))
return isinstance(feature, (OS, Arch, Format))

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2023 Mandiant, Inc. All Rights Reserved.
# Copyright (C) 2021 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
@@ -7,13 +7,16 @@
# See the License for the specific language governing permissions and limitations under the License.
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, Union, Iterator, TypeAlias
from dataclasses import dataclass
import capa.features.address
from capa.features.common import Feature
from capa.features.address import Address, AbsoluteVirtualAddress
from capa.features.address import Address, ThreadAddress, ProcessAddress, DynamicCallAddress, AbsoluteVirtualAddress
# feature extractors may reference functions, BBs, insns by opaque handle values.
# you can use the `.address` property to get and render the address of the feature.
@@ -22,6 +25,24 @@ from capa.features.address import Address, AbsoluteVirtualAddress
# the feature extractor from which they were created.
@dataclass
class SampleHashes:
md5: str
sha1: str
sha256: str
@classmethod
def from_bytes(cls, buf: bytes) -> "SampleHashes":
md5 = hashlib.md5()
sha1 = hashlib.sha1()
sha256 = hashlib.sha256()
md5.update(buf)
sha1.update(buf)
sha256.update(buf)
return cls(md5=md5.hexdigest(), sha1=sha1.hexdigest(), sha256=sha256.hexdigest())
@dataclass
class FunctionHandle:
"""reference to a function recognized by a feature extractor.
@@ -34,7 +55,7 @@ class FunctionHandle:
address: Address
inner: Any
ctx: Dict[str, Any] = dataclasses.field(default_factory=dict)
ctx: dict[str, Any] = dataclasses.field(default_factory=dict)
@dataclass
@@ -52,7 +73,7 @@ class BBHandle:
@dataclass
class InsnHandle:
"""reference to a instruction recognized by a feature extractor.
"""reference to an instruction recognized by a feature extractor.
Attributes:
address: the address of the instruction address.
@@ -63,16 +84,18 @@ class InsnHandle:
inner: Any
class FeatureExtractor:
class StaticFeatureExtractor:
"""
FeatureExtractor defines the interface for fetching features from a sample.
StaticFeatureExtractor defines the interface for fetching features from a
sample without running it; extractors that rely on the execution trace of
a sample must implement the other sibling class, DynamicFeatureExtracor.
There may be multiple backends that support fetching features for capa.
For example, we use vivisect by default, but also want to support saving
and restoring features from a JSON file.
When we restore the features, we'd like to use exactly the same matching logic
to find matching rules.
Therefore, we can define a FeatureExtractor that provides features from the
Therefore, we can define a StaticFeatureExtractor that provides features from the
serialized JSON file and do matching without a binary analysis pass.
Also, this provides a way to hook in an IDA backend.
@@ -81,13 +104,14 @@ class FeatureExtractor:
__metaclass__ = abc.ABCMeta
def __init__(self):
def __init__(self, hashes: SampleHashes):
#
# note: a subclass should define ctor parameters for its own use.
# for example, the Vivisect feature extract might require the vw and/or path.
# this base class doesn't know what to do with that info, though.
#
super().__init__()
self._sample_hashes = hashes
@abc.abstractmethod
def get_base_address(self) -> Union[AbsoluteVirtualAddress, capa.features.address._NoAddress]:
@@ -100,8 +124,14 @@ class FeatureExtractor:
"""
raise NotImplementedError()
def get_sample_hashes(self) -> SampleHashes:
"""
fetch the hashes for the sample contained within the extractor.
"""
return self._sample_hashes
@abc.abstractmethod
def extract_global_features(self) -> Iterator[Tuple[Feature, Address]]:
def extract_global_features(self) -> Iterator[tuple[Feature, Address]]:
"""
extract features found at every scope ("global").
@@ -112,12 +142,12 @@ class FeatureExtractor:
print('0x%x: %s', va, feature)
yields:
Tuple[Feature, Address]: feature and its location
tuple[Feature, Address]: feature and its location
"""
raise NotImplementedError()
@abc.abstractmethod
def extract_file_features(self) -> Iterator[Tuple[Feature, Address]]:
def extract_file_features(self) -> Iterator[tuple[Feature, Address]]:
"""
extract file-scope features.
@@ -128,7 +158,7 @@ class FeatureExtractor:
print('0x%x: %s', va, feature)
yields:
Tuple[Feature, Address]: feature and its location
tuple[Feature, Address]: feature and its location
"""
raise NotImplementedError()
@@ -177,7 +207,7 @@ class FeatureExtractor:
raise KeyError(addr)
@abc.abstractmethod
def extract_function_features(self, f: FunctionHandle) -> Iterator[Tuple[Feature, Address]]:
def extract_function_features(self, f: FunctionHandle) -> Iterator[tuple[Feature, Address]]:
"""
extract function-scope features.
the arguments are opaque values previously provided by `.get_functions()`, etc.
@@ -193,7 +223,7 @@ class FeatureExtractor:
f [FunctionHandle]: an opaque value previously fetched from `.get_functions()`.
yields:
Tuple[Feature, Address]: feature and its location
tuple[Feature, Address]: feature and its location
"""
raise NotImplementedError()
@@ -206,7 +236,7 @@ class FeatureExtractor:
raise NotImplementedError()
@abc.abstractmethod
def extract_basic_block_features(self, f: FunctionHandle, bb: BBHandle) -> Iterator[Tuple[Feature, Address]]:
def extract_basic_block_features(self, f: FunctionHandle, bb: BBHandle) -> Iterator[tuple[Feature, Address]]:
"""
extract basic block-scope features.
the arguments are opaque values previously provided by `.get_functions()`, etc.
@@ -224,7 +254,7 @@ class FeatureExtractor:
bb [BBHandle]: an opaque value previously fetched from `.get_basic_blocks()`.
yields:
Tuple[Feature, Address]: feature and its location
tuple[Feature, Address]: feature and its location
"""
raise NotImplementedError()
@@ -239,7 +269,7 @@ class FeatureExtractor:
@abc.abstractmethod
def extract_insn_features(
self, f: FunctionHandle, bb: BBHandle, insn: InsnHandle
) -> Iterator[Tuple[Feature, Address]]:
) -> Iterator[tuple[Feature, Address]]:
"""
extract instruction-scope features.
the arguments are opaque values previously provided by `.get_functions()`, etc.
@@ -259,6 +289,212 @@ class FeatureExtractor:
insn [InsnHandle]: an opaque value previously fetched from `.get_instructions()`.
yields:
Tuple[Feature, Address]: feature and its location
tuple[Feature, Address]: feature and its location
"""
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:
"""
reference to a process extracted by the sandbox.
Attributes:
address: process's address (pid)
inner: sandbox-specific data
"""
address: ProcessAddress
inner: Any
@dataclass
class ThreadHandle:
"""
reference to a thread extracted by the sandbox.
Attributes:
address: thread's address (tid)
inner: sandbox-specific data
"""
address: ThreadAddress
inner: Any
@dataclass
class CallHandle:
"""
reference to an api call extracted by the sandbox.
Attributes:
address: call's address, such as event index or id
inner: sandbox-specific data
"""
address: DynamicCallAddress
inner: Any
class DynamicFeatureExtractor:
"""
DynamicFeatureExtractor defines the interface for fetching features from a
sandbox' analysis of a sample; extractors that rely on statically analyzing
a sample must implement the sibling extractor, StaticFeatureExtractor.
Features are grouped mainly into threads that alongside their meta-features are also grouped into
processes (that also have their own features). Other scopes (such as function and file) may also apply
for a specific sandbox.
This class is not instantiated directly; it is the base class for other implementations.
"""
__metaclass__ = abc.ABCMeta
def __init__(self, hashes: SampleHashes):
#
# note: a subclass should define ctor parameters for its own use.
# for example, the Vivisect feature extract might require the vw and/or path.
# this base class doesn't know what to do with that info, though.
#
super().__init__()
self._sample_hashes = hashes
def get_sample_hashes(self) -> SampleHashes:
"""
fetch the hashes for the sample contained within the extractor.
"""
return self._sample_hashes
@abc.abstractmethod
def extract_global_features(self) -> Iterator[tuple[Feature, Address]]:
"""
extract features found at every scope ("global").
example::
extractor = CapeFeatureExtractor.from_report(json.loads(buf))
for feature, addr in extractor.get_global_features():
print(addr, feature)
yields:
tuple[Feature, Address]: feature and its location
"""
raise NotImplementedError()
@abc.abstractmethod
def extract_file_features(self) -> Iterator[tuple[Feature, Address]]:
"""
extract file-scope features.
example::
extractor = CapeFeatureExtractor.from_report(json.loads(buf))
for feature, addr in extractor.get_file_features():
print(addr, feature)
yields:
tuple[Feature, Address]: feature and its location
"""
raise NotImplementedError()
@abc.abstractmethod
def get_processes(self) -> Iterator[ProcessHandle]:
"""
Enumerate processes in the trace.
"""
raise NotImplementedError()
@abc.abstractmethod
def extract_process_features(self, ph: ProcessHandle) -> Iterator[tuple[Feature, Address]]:
"""
Yields all the features of a process. These include:
- file features of the process' image
"""
raise NotImplementedError()
@abc.abstractmethod
def get_process_name(self, ph: ProcessHandle) -> str:
"""
Returns the human-readable name for the given process,
such as the filename.
"""
raise NotImplementedError()
@abc.abstractmethod
def get_threads(self, ph: ProcessHandle) -> Iterator[ThreadHandle]:
"""
Enumerate threads in the given process.
"""
raise NotImplementedError()
@abc.abstractmethod
def extract_thread_features(self, ph: ProcessHandle, th: ThreadHandle) -> Iterator[tuple[Feature, Address]]:
"""
Yields all the features of a thread. These include:
- sequenced api traces
"""
raise NotImplementedError()
@abc.abstractmethod
def get_calls(self, ph: ProcessHandle, th: ThreadHandle) -> Iterator[CallHandle]:
"""
Enumerate calls in the given thread
"""
raise NotImplementedError()
@abc.abstractmethod
def extract_call_features(
self, ph: ProcessHandle, th: ThreadHandle, ch: CallHandle
) -> Iterator[tuple[Feature, Address]]:
"""
Yields all features of a call. These include:
- api name
- bytes/strings/numbers extracted from arguments
"""
raise NotImplementedError()
@abc.abstractmethod
def get_call_name(self, ph: ProcessHandle, th: ThreadHandle, ch: CallHandle) -> str:
"""
Returns the human-readable name for the given call,
such as as rendered API log entry, like:
Foo(1, "two", b"\x00\x11") -> -1
"""
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]

View File

@@ -0,0 +1,418 @@
# Copyright (C) 2023 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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.
"""
Proto files generated via protobuf v24.4:
protoc --python_out=. --mypy_out=. binexport2.proto
from BinExport2 at 6916731d5f6693c4a4f0a052501fd3bd92cfd08b
https://github.com/google/binexport/blob/6916731/binexport2.proto
"""
import io
import hashlib
import logging
import contextlib
from typing import Iterator
from pathlib import Path
from collections import defaultdict
from dataclasses import dataclass
from pefile import PE
from elftools.elf.elffile import ELFFile
import capa.features.common
import capa.features.extractors.common
import capa.features.extractors.binexport2.helpers
from capa.features.extractors.binexport2.binexport2_pb2 import BinExport2
logger = logging.getLogger(__name__)
def get_binexport2(sample: Path) -> BinExport2:
be2: BinExport2 = BinExport2()
be2.ParseFromString(sample.read_bytes())
return be2
def compute_common_prefix_length(m: str, n: str) -> int:
# ensure #m < #n
if len(n) < len(m):
m, n = n, m
for i, c in enumerate(m):
if n[i] != c:
return i
return len(m)
def get_sample_from_binexport2(input_file: Path, be2: BinExport2, search_paths: list[Path]) -> Path:
"""attempt to find the sample file, given a BinExport2 file.
searches in the same directory as the BinExport2 file, and then in search_paths.
"""
def filename_similarity_key(p: Path) -> tuple[int, str]:
# note closure over input_file.
# sort first by length of common prefix, then by name (for stability)
return (compute_common_prefix_length(p.name, input_file.name), p.name)
wanted_sha256: str = be2.meta_information.executable_id.lower()
input_directory: Path = input_file.parent
siblings: list[Path] = [p for p in input_directory.iterdir() if p.is_file()]
siblings.sort(key=filename_similarity_key, reverse=True)
for sibling in siblings:
# e.g. with open IDA files in the same directory on Windows
with contextlib.suppress(PermissionError):
if hashlib.sha256(sibling.read_bytes()).hexdigest().lower() == wanted_sha256:
return sibling
for search_path in search_paths:
candidates: list[Path] = [p for p in search_path.iterdir() if p.is_file()]
candidates.sort(key=filename_similarity_key, reverse=True)
for candidate in candidates:
with contextlib.suppress(PermissionError):
if hashlib.sha256(candidate.read_bytes()).hexdigest().lower() == wanted_sha256:
return candidate
raise ValueError("cannot find sample, you may specify the path using the CAPA_SAMPLES_DIR environment variable")
class BinExport2Index:
def __init__(self, be2: BinExport2):
self.be2: BinExport2 = be2
self.callers_by_vertex_index: dict[int, list[int]] = defaultdict(list)
self.callees_by_vertex_index: dict[int, list[int]] = defaultdict(list)
# note: flow graph != call graph (vertex)
self.flow_graph_index_by_address: dict[int, int] = {}
self.flow_graph_address_by_index: dict[int, int] = {}
# edges that come from the given basic block
self.source_edges_by_basic_block_index: dict[int, list[BinExport2.FlowGraph.Edge]] = defaultdict(list)
# edges that end up at the given basic block
self.target_edges_by_basic_block_index: dict[int, list[BinExport2.FlowGraph.Edge]] = defaultdict(list)
self.vertex_index_by_address: dict[int, int] = {}
self.data_reference_index_by_source_instruction_index: dict[int, list[int]] = defaultdict(list)
self.data_reference_index_by_target_address: dict[int, list[int]] = defaultdict(list)
self.string_reference_index_by_source_instruction_index: dict[int, list[int]] = defaultdict(list)
self.insn_address_by_index: dict[int, int] = {}
self.insn_index_by_address: dict[int, int] = {}
self.insn_by_address: dict[int, BinExport2.Instruction] = {}
# must index instructions first
self._index_insn_addresses()
self._index_vertex_edges()
self._index_flow_graph_nodes()
self._index_flow_graph_edges()
self._index_call_graph_vertices()
self._index_data_references()
self._index_string_references()
def get_insn_address(self, insn_index: int) -> int:
assert insn_index in self.insn_address_by_index, f"insn must be indexed, missing {insn_index}"
return self.insn_address_by_index[insn_index]
def get_basic_block_address(self, basic_block_index: int) -> int:
basic_block: BinExport2.BasicBlock = self.be2.basic_block[basic_block_index]
first_instruction_index: int = next(self.instruction_indices(basic_block))
return self.get_insn_address(first_instruction_index)
def _index_vertex_edges(self):
for edge in self.be2.call_graph.edge:
if not edge.source_vertex_index:
continue
if not edge.target_vertex_index:
continue
self.callers_by_vertex_index[edge.target_vertex_index].append(edge.source_vertex_index)
self.callees_by_vertex_index[edge.source_vertex_index].append(edge.target_vertex_index)
def _index_flow_graph_nodes(self):
for flow_graph_index, flow_graph in enumerate(self.be2.flow_graph):
function_address: int = self.get_basic_block_address(flow_graph.entry_basic_block_index)
self.flow_graph_index_by_address[function_address] = flow_graph_index
self.flow_graph_address_by_index[flow_graph_index] = function_address
def _index_flow_graph_edges(self):
for flow_graph in self.be2.flow_graph:
for edge in flow_graph.edge:
if not edge.HasField("source_basic_block_index") or not edge.HasField("target_basic_block_index"):
continue
self.source_edges_by_basic_block_index[edge.source_basic_block_index].append(edge)
self.target_edges_by_basic_block_index[edge.target_basic_block_index].append(edge)
def _index_call_graph_vertices(self):
for vertex_index, vertex in enumerate(self.be2.call_graph.vertex):
if not vertex.HasField("address"):
continue
vertex_address: int = vertex.address
self.vertex_index_by_address[vertex_address] = vertex_index
def _index_data_references(self):
for data_reference_index, data_reference in enumerate(self.be2.data_reference):
self.data_reference_index_by_source_instruction_index[data_reference.instruction_index].append(
data_reference_index
)
self.data_reference_index_by_target_address[data_reference.address].append(data_reference_index)
def _index_string_references(self):
for string_reference_index, string_reference in enumerate(self.be2.string_reference):
self.string_reference_index_by_source_instruction_index[string_reference.instruction_index].append(
string_reference_index
)
def _index_insn_addresses(self):
# see https://github.com/google/binexport/blob/39f6445c232bb5caf5c4a2a996de91dfa20c48e8/binexport.cc#L45
if len(self.be2.instruction) == 0:
return
assert self.be2.instruction[0].HasField("address"), "first insn must have explicit address"
addr: int = 0
next_addr: int = 0
for idx, insn in enumerate(self.be2.instruction):
if insn.HasField("address"):
addr = insn.address
next_addr = addr + len(insn.raw_bytes)
else:
addr = next_addr
next_addr += len(insn.raw_bytes)
self.insn_address_by_index[idx] = addr
self.insn_index_by_address[addr] = idx
self.insn_by_address[addr] = insn
@staticmethod
def instruction_indices(basic_block: BinExport2.BasicBlock) -> Iterator[int]:
"""
For a given basic block, enumerate the instruction indices.
"""
for index_range in basic_block.instruction_index:
if not index_range.HasField("end_index"):
yield index_range.begin_index
continue
else:
yield from range(index_range.begin_index, index_range.end_index)
def basic_block_instructions(
self, basic_block: BinExport2.BasicBlock
) -> Iterator[tuple[int, BinExport2.Instruction, int]]:
"""
For a given basic block, enumerate the instruction indices,
the instruction instances, and their addresses.
"""
for instruction_index in self.instruction_indices(basic_block):
instruction: BinExport2.Instruction = self.be2.instruction[instruction_index]
instruction_address: int = self.get_insn_address(instruction_index)
yield instruction_index, instruction, instruction_address
def get_function_name_by_vertex(self, vertex_index: int) -> str:
vertex: BinExport2.CallGraph.Vertex = self.be2.call_graph.vertex[vertex_index]
name: str = f"sub_{vertex.address:x}"
if vertex.HasField("mangled_name"):
name = vertex.mangled_name
if vertex.HasField("demangled_name"):
name = vertex.demangled_name
if vertex.HasField("library_index"):
library: BinExport2.Library = self.be2.library[vertex.library_index]
if library.HasField("name"):
name = f"{library.name}!{name}"
return name
def get_function_name_by_address(self, address: int) -> str:
if address not in self.vertex_index_by_address:
return ""
vertex_index: int = self.vertex_index_by_address[address]
return self.get_function_name_by_vertex(vertex_index)
def get_instruction_by_address(self, address: int) -> BinExport2.Instruction:
assert address in self.insn_by_address, f"address must be indexed, missing {address:x}"
return self.insn_by_address[address]
class BinExport2Analysis:
def __init__(self, be2: BinExport2, idx: BinExport2Index, buf: bytes):
self.be2: BinExport2 = be2
self.idx: BinExport2Index = idx
self.buf: bytes = buf
self.base_address: int = 0
self.thunks: dict[int, int] = {}
self._find_base_address()
self._compute_thunks()
def _find_base_address(self):
sections_with_perms: Iterator[BinExport2.Section] = filter(
lambda s: s.flag_r or s.flag_w or s.flag_x, self.be2.section
)
# assume the lowest address is the base address.
# this works as long as BinExport doesn't record other
# libraries mapped into memory.
self.base_address = min(s.address for s in sections_with_perms)
logger.debug("found base address: %x", self.base_address)
def _compute_thunks(self):
for addr, idx in self.idx.vertex_index_by_address.items():
vertex: BinExport2.CallGraph.Vertex = self.be2.call_graph.vertex[idx]
if not capa.features.extractors.binexport2.helpers.is_vertex_type(
vertex, BinExport2.CallGraph.Vertex.Type.THUNK
):
continue
curr_idx: int = idx
for _ in range(capa.features.common.THUNK_CHAIN_DEPTH_DELTA):
thunk_callees: list[int] = self.idx.callees_by_vertex_index[curr_idx]
# If this doesn't hold, then it doesn't seem like this is a thunk,
# because either, len is:
# 0 and the thunk doesn't point to anything or is indirect, like `call eax`, or
# >1 and the thunk may end up at many functions.
# In any case, this doesn't appear to be the sort of thunk we're looking for.
if len(thunk_callees) != 1:
break
thunked_idx: int = thunk_callees[0]
thunked_vertex: BinExport2.CallGraph.Vertex = self.be2.call_graph.vertex[thunked_idx]
if not capa.features.extractors.binexport2.helpers.is_vertex_type(
thunked_vertex, BinExport2.CallGraph.Vertex.Type.THUNK
):
assert thunked_vertex.HasField("address")
self.thunks[addr] = thunked_vertex.address
break
curr_idx = thunked_idx
@dataclass
class MemoryRegion:
# location of the bytes, potentially relative to a base address
address: int
buf: bytes
@property
def end(self) -> int:
return self.address + len(self.buf)
def contains(self, address: int) -> bool:
# note: address must be relative to any base address
return self.address <= address < self.end
class ReadMemoryError(ValueError): ...
class AddressNotMappedError(ReadMemoryError): ...
@dataclass
class AddressSpace:
base_address: int
memory_regions: tuple[MemoryRegion, ...]
def read_memory(self, address: int, length: int) -> bytes:
rva: int = address - self.base_address
for region in self.memory_regions:
if region.contains(rva):
offset: int = rva - region.address
return region.buf[offset : offset + length]
raise AddressNotMappedError(address)
@classmethod
def from_pe(cls, pe: PE, base_address: int):
regions: list[MemoryRegion] = []
for section in pe.sections:
address: int = section.VirtualAddress
size: int = section.Misc_VirtualSize
buf: bytes = section.get_data()
if len(buf) != size:
# pad the section with NULLs
# assume page alignment is already handled.
# might need more hardening here.
buf += b"\x00" * (size - len(buf))
regions.append(MemoryRegion(address, buf))
return cls(base_address, tuple(regions))
@classmethod
def from_elf(cls, elf: ELFFile, base_address: int):
regions: list[MemoryRegion] = []
# ELF segments are for runtime data,
# ELF sections are for link-time data.
for segment in elf.iter_segments():
# assume p_align is consistent with addresses here.
# otherwise, should harden this loader.
segment_rva: int = segment.header.p_vaddr
segment_size: int = segment.header.p_memsz
segment_data: bytes = segment.data()
if len(segment_data) < segment_size:
# pad the section with NULLs
# assume page alignment is already handled.
# might need more hardening here.
segment_data += b"\x00" * (segment_size - len(segment_data))
regions.append(MemoryRegion(segment_rva, segment_data))
return cls(base_address, tuple(regions))
@classmethod
def from_buf(cls, buf: bytes, base_address: int):
if buf.startswith(capa.features.extractors.common.MATCH_PE):
pe: PE = PE(data=buf)
return cls.from_pe(pe, base_address)
elif buf.startswith(capa.features.extractors.common.MATCH_ELF):
elf: ELFFile = ELFFile(io.BytesIO(buf))
return cls.from_elf(elf, base_address)
else:
raise NotImplementedError("file format address space")
@dataclass
class AnalysisContext:
sample_bytes: bytes
be2: BinExport2
idx: BinExport2Index
analysis: BinExport2Analysis
address_space: AddressSpace
@dataclass
class FunctionContext:
ctx: AnalysisContext
flow_graph_index: int
format: set[str]
os: set[str]
arch: set[str]
@dataclass
class BasicBlockContext:
basic_block_index: int
@dataclass
class InstructionContext:
instruction_index: int

View File

@@ -0,0 +1,15 @@
# Copyright (C) 2024 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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.
from capa.features.extractors.binexport2.binexport2_pb2 import BinExport2
def is_stack_register_expression(be2: BinExport2, expression: BinExport2.Expression) -> bool:
return bool(
expression and expression.type == BinExport2.Expression.REGISTER and expression.symbol.lower().endswith("sp")
)

View File

@@ -0,0 +1,155 @@
# Copyright (C) 2024 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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.
import logging
from typing import Iterator, Optional
import capa.features.extractors.binexport2.helpers
from capa.features.insn import MAX_STRUCTURE_SIZE, Number, Offset, OperandNumber, OperandOffset
from capa.features.common import Feature, Characteristic
from capa.features.address import Address
from capa.features.extractors.binexport2 import FunctionContext, InstructionContext
from capa.features.extractors.base_extractor import BBHandle, InsnHandle, FunctionHandle
from capa.features.extractors.binexport2.helpers import (
BinExport2InstructionPatternMatcher,
mask_immediate,
is_address_mapped,
get_instruction_mnemonic,
get_operand_register_expression,
get_operand_immediate_expression,
)
from capa.features.extractors.binexport2.binexport2_pb2 import BinExport2
from capa.features.extractors.binexport2.arch.arm.helpers import is_stack_register_expression
logger = logging.getLogger(__name__)
def extract_insn_number_features(
fh: FunctionHandle, _bbh: BBHandle, ih: InsnHandle
) -> Iterator[tuple[Feature, Address]]:
fhi: FunctionContext = fh.inner
ii: InstructionContext = ih.inner
be2: BinExport2 = fhi.ctx.be2
instruction_index: int = ii.instruction_index
instruction: BinExport2.Instruction = be2.instruction[instruction_index]
if len(instruction.operand_index) == 0:
# skip things like:
# .text:0040116e leave
return
mnemonic: str = get_instruction_mnemonic(be2, instruction)
if mnemonic in ("add", "sub"):
assert len(instruction.operand_index) == 3
operand1_expression: Optional[BinExport2.Expression] = get_operand_register_expression(
be2, be2.operand[instruction.operand_index[1]]
)
if operand1_expression and is_stack_register_expression(be2, operand1_expression):
# skip things like:
# add x0,sp,#0x8
return
for i, operand_index in enumerate(instruction.operand_index):
operand: BinExport2.Operand = be2.operand[operand_index]
immediate_expression: Optional[BinExport2.Expression] = get_operand_immediate_expression(be2, operand)
if not immediate_expression:
continue
value: int = mask_immediate(fhi.arch, immediate_expression.immediate)
if is_address_mapped(be2, value):
continue
yield Number(value), ih.address
yield OperandNumber(i, value), ih.address
if mnemonic == "add" and i == 2:
if 0 < value < MAX_STRUCTURE_SIZE:
yield Offset(value), ih.address
yield OperandOffset(i, value), ih.address
OFFSET_PATTERNS = BinExport2InstructionPatternMatcher.from_str(
"""
ldr|ldrb|ldrh|ldrsb|ldrsh|ldrex|ldrd|str|strb|strh|strex|strd reg, [reg(not-stack), #int] ; capture #int
ldr|ldrb|ldrh|ldrsb|ldrsh|ldrex|ldrd|str|strb|strh|strex|strd reg, [reg(not-stack), #int]! ; capture #int
ldr|ldrb|ldrh|ldrsb|ldrsh|ldrex|ldrd|str|strb|strh|strex|strd reg, [reg(not-stack)], #int ; capture #int
ldp|ldpd|stp|stpd reg, reg, [reg(not-stack), #int] ; capture #int
ldp|ldpd|stp|stpd reg, reg, [reg(not-stack), #int]! ; capture #int
ldp|ldpd|stp|stpd reg, reg, [reg(not-stack)], #int ; capture #int
"""
)
def extract_insn_offset_features(
fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle
) -> Iterator[tuple[Feature, Address]]:
fhi: FunctionContext = fh.inner
ii: InstructionContext = ih.inner
be2: BinExport2 = fhi.ctx.be2
match = OFFSET_PATTERNS.match_with_be2(be2, ii.instruction_index)
if not match:
return
value = match.expression.immediate
value = mask_immediate(fhi.arch, value)
if not is_address_mapped(be2, value):
value = capa.features.extractors.binexport2.helpers.twos_complement(fhi.arch, value)
yield Offset(value), ih.address
yield OperandOffset(match.operand_index, value), ih.address
NZXOR_PATTERNS = BinExport2InstructionPatternMatcher.from_str(
"""
eor reg, reg, reg
eor reg, reg, #int
"""
)
def extract_insn_nzxor_characteristic_features(
fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle
) -> Iterator[tuple[Feature, Address]]:
fhi: FunctionContext = fh.inner
ii: InstructionContext = ih.inner
be2: BinExport2 = fhi.ctx.be2
if NZXOR_PATTERNS.match_with_be2(be2, ii.instruction_index) is None:
return
instruction: BinExport2.Instruction = be2.instruction[ii.instruction_index]
# guaranteed to be simple int/reg operands
# so we don't have to realize the tree/list.
operands: list[BinExport2.Operand] = [be2.operand[operand_index] for operand_index in instruction.operand_index]
if operands[1] != operands[2]:
yield Characteristic("nzxor"), ih.address
INDIRECT_CALL_PATTERNS = BinExport2InstructionPatternMatcher.from_str(
"""
blx|bx|blr reg
"""
)
def extract_function_indirect_call_characteristic_features(
fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle
) -> Iterator[tuple[Feature, Address]]:
fhi: FunctionContext = fh.inner
ii: InstructionContext = ih.inner
be2: BinExport2 = fhi.ctx.be2
if INDIRECT_CALL_PATTERNS.match_with_be2(be2, ii.instruction_index) is not None:
yield Characteristic("indirect call"), ih.address

View File

@@ -0,0 +1,135 @@
# Copyright (C) 2024 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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.
from typing import Optional
from dataclasses import dataclass
from capa.features.extractors.binexport2.helpers import get_operand_expressions
from capa.features.extractors.binexport2.binexport2_pb2 import BinExport2
# security cookie checks may perform non-zeroing XORs, these are expected within a certain
# byte range within the first and returning basic blocks, this helps to reduce FP features
SECURITY_COOKIE_BYTES_DELTA: int = 0x40
@dataclass
class OperandPhraseInfo:
scale: Optional[BinExport2.Expression] = None
index: Optional[BinExport2.Expression] = None
base: Optional[BinExport2.Expression] = None
displacement: Optional[BinExport2.Expression] = None
def get_operand_phrase_info(be2: BinExport2, operand: BinExport2.Operand) -> Optional[OperandPhraseInfo]:
# assume the following (see https://blog.yossarian.net/2020/06/13/How-x86_64-addresses-memory):
#
# Scale: A 2-bit constant factor
# Index: Any general purpose register
# Base: Any general purpose register
# Displacement: An integral offset
expressions: list[BinExport2.Expression] = get_operand_expressions(be2, operand)
# skip expression up to and including BinExport2.Expression.DEREFERENCE, assume caller
# has checked for BinExport2.Expression.DEREFERENCE
for i, expression in enumerate(expressions):
if expression.type == BinExport2.Expression.DEREFERENCE:
expressions = expressions[i + 1 :]
break
expression0: BinExport2.Expression
expression1: BinExport2.Expression
expression2: BinExport2.Expression
expression3: BinExport2.Expression
expression4: BinExport2.Expression
if len(expressions) == 1:
expression0 = expressions[0]
assert (
expression0.type == BinExport2.Expression.IMMEDIATE_INT
or expression0.type == BinExport2.Expression.REGISTER
)
if expression0.type == BinExport2.Expression.IMMEDIATE_INT:
# Displacement
return OperandPhraseInfo(displacement=expression0)
elif expression0.type == BinExport2.Expression.REGISTER:
# Base
return OperandPhraseInfo(base=expression0)
elif len(expressions) == 3:
expression0 = expressions[0]
expression1 = expressions[1]
expression2 = expressions[2]
assert expression0.type == BinExport2.Expression.REGISTER
assert expression1.type == BinExport2.Expression.OPERATOR
assert (
expression2.type == BinExport2.Expression.IMMEDIATE_INT
or expression2.type == BinExport2.Expression.REGISTER
)
if expression2.type == BinExport2.Expression.REGISTER:
# Base + Index
return OperandPhraseInfo(base=expression0, index=expression2)
elif expression2.type == BinExport2.Expression.IMMEDIATE_INT:
# Base + Displacement
return OperandPhraseInfo(base=expression0, displacement=expression2)
elif len(expressions) == 5:
expression0 = expressions[0]
expression1 = expressions[1]
expression2 = expressions[2]
expression3 = expressions[3]
expression4 = expressions[4]
assert expression0.type == BinExport2.Expression.REGISTER
assert expression1.type == BinExport2.Expression.OPERATOR
assert (
expression2.type == BinExport2.Expression.REGISTER
or expression2.type == BinExport2.Expression.IMMEDIATE_INT
)
assert expression3.type == BinExport2.Expression.OPERATOR
assert expression4.type == BinExport2.Expression.IMMEDIATE_INT
if expression1.symbol == "+" and expression3.symbol == "+":
# Base + Index + Displacement
return OperandPhraseInfo(base=expression0, index=expression2, displacement=expression4)
elif expression1.symbol == "+" and expression3.symbol == "*":
# Base + (Index * Scale)
return OperandPhraseInfo(base=expression0, index=expression2, scale=expression3)
elif expression1.symbol == "*" and expression3.symbol == "+":
# (Index * Scale) + Displacement
return OperandPhraseInfo(index=expression0, scale=expression2, displacement=expression3)
else:
raise NotImplementedError(expression1.symbol, expression3.symbol)
elif len(expressions) == 7:
expression0 = expressions[0]
expression1 = expressions[1]
expression2 = expressions[2]
expression3 = expressions[3]
expression4 = expressions[4]
expression5 = expressions[5]
expression6 = expressions[6]
assert expression0.type == BinExport2.Expression.REGISTER
assert expression1.type == BinExport2.Expression.OPERATOR
assert expression2.type == BinExport2.Expression.REGISTER
assert expression3.type == BinExport2.Expression.OPERATOR
assert expression4.type == BinExport2.Expression.IMMEDIATE_INT
assert expression5.type == BinExport2.Expression.OPERATOR
assert expression6.type == BinExport2.Expression.IMMEDIATE_INT
# Base + (Index * Scale) + Displacement
return OperandPhraseInfo(base=expression0, index=expression2, scale=expression4, displacement=expression6)
else:
raise NotImplementedError(len(expressions))
return None

View File

@@ -0,0 +1,248 @@
# Copyright (C) 2024 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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.
import logging
from typing import Iterator
import capa.features.extractors.strings
import capa.features.extractors.binexport2.helpers
from capa.features.insn import MAX_STRUCTURE_SIZE, Number, Offset, OperandNumber, OperandOffset
from capa.features.common import Feature, Characteristic
from capa.features.address import Address
from capa.features.extractors.binexport2 import BinExport2Index, FunctionContext, BasicBlockContext, InstructionContext
from capa.features.extractors.base_extractor import BBHandle, InsnHandle, FunctionHandle
from capa.features.extractors.binexport2.helpers import (
BinExport2InstructionPatternMatcher,
mask_immediate,
is_address_mapped,
get_instruction_mnemonic,
)
from capa.features.extractors.binexport2.binexport2_pb2 import BinExport2
from capa.features.extractors.binexport2.arch.intel.helpers import SECURITY_COOKIE_BYTES_DELTA
logger = logging.getLogger(__name__)
IGNORE_NUMBER_PATTERNS = BinExport2InstructionPatternMatcher.from_str(
"""
ret #int
retn #int
add reg(stack), #int
sub reg(stack), #int
"""
)
NUMBER_PATTERNS = BinExport2InstructionPatternMatcher.from_str(
"""
push #int0 ; capture #int0
# its a little tedious to enumerate all the address forms
# but at least we are explicit
cmp|and|or|test|mov|add|adc|sub|shl|shr|sal|sar reg, #int0 ; capture #int0
cmp|and|or|test|mov|add|adc|sub|shl|shr|sal|sar [reg], #int0 ; capture #int0
cmp|and|or|test|mov|add|adc|sub|shl|shr|sal|sar [#int], #int0 ; capture #int0
cmp|and|or|test|mov|add|adc|sub|shl|shr|sal|sar [reg + #int], #int0 ; capture #int0
cmp|and|or|test|mov|add|adc|sub|shl|shr|sal|sar [reg + reg + #int], #int0 ; capture #int0
cmp|and|or|test|mov|add|adc|sub|shl|shr|sal|sar [reg + reg * #int], #int0 ; capture #int0
cmp|and|or|test|mov|add|adc|sub|shl|shr|sal|sar [reg + reg * #int + #int], #int0 ; capture #int0
imul reg, reg, #int ; capture #int
# note that int is first
cmp|test #int0, reg ; capture #int0
# imagine reg is zero'd out, then this is like `mov reg, #int`
# which is not uncommon.
lea reg, [reg + #int] ; capture #int
"""
)
def extract_insn_number_features(
fh: FunctionHandle, _bbh: BBHandle, ih: InsnHandle
) -> Iterator[tuple[Feature, Address]]:
fhi: FunctionContext = fh.inner
ii: InstructionContext = ih.inner
be2: BinExport2 = fhi.ctx.be2
if IGNORE_NUMBER_PATTERNS.match_with_be2(be2, ii.instruction_index):
return
match = NUMBER_PATTERNS.match_with_be2(be2, ii.instruction_index)
if not match:
return
value: int = mask_immediate(fhi.arch, match.expression.immediate)
if is_address_mapped(be2, value):
return
yield Number(value), ih.address
yield OperandNumber(match.operand_index, value), ih.address
instruction_index: int = ii.instruction_index
instruction: BinExport2.Instruction = be2.instruction[instruction_index]
mnemonic: str = get_instruction_mnemonic(be2, instruction)
if mnemonic.startswith("add"):
if 0 < value < MAX_STRUCTURE_SIZE:
yield Offset(value), ih.address
yield OperandOffset(match.operand_index, value), ih.address
OFFSET_PATTERNS = BinExport2InstructionPatternMatcher.from_str(
"""
mov|movzx|movsb|cmp [reg + reg * #int + #int0], #int ; capture #int0
mov|movzx|movsb|cmp [reg * #int + #int0], #int ; capture #int0
mov|movzx|movsb|cmp [reg + reg + #int0], #int ; capture #int0
mov|movzx|movsb|cmp [reg(not-stack) + #int0], #int ; capture #int0
mov|movzx|movsb|cmp [reg + reg * #int + #int0], reg ; capture #int0
mov|movzx|movsb|cmp [reg * #int + #int0], reg ; capture #int0
mov|movzx|movsb|cmp [reg + reg + #int0], reg ; capture #int0
mov|movzx|movsb|cmp [reg(not-stack) + #int0], reg ; capture #int0
mov|movzx|movsb|cmp|lea reg, [reg + reg * #int + #int0] ; capture #int0
mov|movzx|movsb|cmp|lea reg, [reg * #int + #int0] ; capture #int0
mov|movzx|movsb|cmp|lea reg, [reg + reg + #int0] ; capture #int0
mov|movzx|movsb|cmp|lea reg, [reg(not-stack) + #int0] ; capture #int0
"""
)
# these are patterns that access offset 0 from some pointer
# (pointer is not the stack pointer).
OFFSET_ZERO_PATTERNS = BinExport2InstructionPatternMatcher.from_str(
"""
mov|movzx|movsb [reg(not-stack)], reg
mov|movzx|movsb [reg(not-stack)], #int
lea reg, [reg(not-stack)]
"""
)
def extract_insn_offset_features(
fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle
) -> Iterator[tuple[Feature, Address]]:
fhi: FunctionContext = fh.inner
ii: InstructionContext = ih.inner
be2: BinExport2 = fhi.ctx.be2
match = OFFSET_PATTERNS.match_with_be2(be2, ii.instruction_index)
if not match:
match = OFFSET_ZERO_PATTERNS.match_with_be2(be2, ii.instruction_index)
if not match:
return
yield Offset(0), ih.address
yield OperandOffset(match.operand_index, 0), ih.address
value = mask_immediate(fhi.arch, match.expression.immediate)
if is_address_mapped(be2, value):
return
value = capa.features.extractors.binexport2.helpers.twos_complement(fhi.arch, value, 32)
yield Offset(value), ih.address
yield OperandOffset(match.operand_index, value), ih.address
def is_security_cookie(
fhi: FunctionContext,
bbi: BasicBlockContext,
instruction_address: int,
instruction: BinExport2.Instruction,
) -> bool:
"""
check if an instruction is related to security cookie checks.
"""
be2: BinExport2 = fhi.ctx.be2
idx: BinExport2Index = fhi.ctx.idx
# security cookie check should use SP or BP
op1: BinExport2.Operand = be2.operand[instruction.operand_index[1]]
op1_exprs: list[BinExport2.Expression] = [be2.expression[expr_i] for expr_i in op1.expression_index]
if all(expr.symbol.lower() not in ("bp", "esp", "ebp", "rbp", "rsp") for expr in op1_exprs):
return False
# check_nzxor_security_cookie_delta
# if insn falls at the start of first entry block of the parent function.
flow_graph: BinExport2.FlowGraph = be2.flow_graph[fhi.flow_graph_index]
basic_block_index: int = bbi.basic_block_index
bb: BinExport2.BasicBlock = be2.basic_block[basic_block_index]
if flow_graph.entry_basic_block_index == basic_block_index:
first_addr: int = min((idx.insn_address_by_index[ir.begin_index] for ir in bb.instruction_index))
if instruction_address < first_addr + SECURITY_COOKIE_BYTES_DELTA:
return True
# or insn falls at the end before return in a terminal basic block.
if basic_block_index not in (e.source_basic_block_index for e in flow_graph.edge):
last_addr: int = max((idx.insn_address_by_index[ir.end_index - 1] for ir in bb.instruction_index))
if instruction_address > last_addr - SECURITY_COOKIE_BYTES_DELTA:
return True
return False
NZXOR_PATTERNS = BinExport2InstructionPatternMatcher.from_str(
"""
xor|xorpd|xorps|pxor reg, reg
xor|xorpd|xorps|pxor reg, #int
"""
)
def extract_insn_nzxor_characteristic_features(
fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle
) -> Iterator[tuple[Feature, Address]]:
"""
parse non-zeroing XOR instruction from the given instruction.
ignore expected non-zeroing XORs, e.g. security cookies.
"""
fhi: FunctionContext = fh.inner
ii: InstructionContext = ih.inner
be2: BinExport2 = fhi.ctx.be2
idx: BinExport2Index = fhi.ctx.idx
if NZXOR_PATTERNS.match_with_be2(be2, ii.instruction_index) is None:
return
instruction: BinExport2.Instruction = be2.instruction[ii.instruction_index]
# guaranteed to be simple int/reg operands
# so we don't have to realize the tree/list.
operands: list[BinExport2.Operand] = [be2.operand[operand_index] for operand_index in instruction.operand_index]
if operands[0] == operands[1]:
return
instruction_address: int = idx.insn_address_by_index[ii.instruction_index]
if is_security_cookie(fhi, bbh.inner, instruction_address, instruction):
return
yield Characteristic("nzxor"), ih.address
INDIRECT_CALL_PATTERNS = BinExport2InstructionPatternMatcher.from_str(
"""
call|jmp reg0
call|jmp [reg + reg * #int + #int]
call|jmp [reg + reg * #int]
call|jmp [reg * #int + #int]
call|jmp [reg + reg + #int]
call|jmp [reg + #int]
call|jmp [reg]
"""
)
def extract_function_indirect_call_characteristic_features(
fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle
) -> Iterator[tuple[Feature, Address]]:
fhi: FunctionContext = fh.inner
ii: InstructionContext = ih.inner
be2: BinExport2 = fhi.ctx.be2
match = INDIRECT_CALL_PATTERNS.match_with_be2(be2, ii.instruction_index)
if match is None:
return
yield Characteristic("indirect call"), ih.address

View File

@@ -0,0 +1,40 @@
# Copyright (C) 2023 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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.
from typing import Iterator
from capa.features.common import Feature, Characteristic
from capa.features.address import Address, AbsoluteVirtualAddress
from capa.features.basicblock import BasicBlock
from capa.features.extractors.binexport2 import FunctionContext, BasicBlockContext
from capa.features.extractors.base_extractor import BBHandle, FunctionHandle
from capa.features.extractors.binexport2.binexport2_pb2 import BinExport2
def extract_bb_tight_loop(fh: FunctionHandle, bbh: BBHandle) -> Iterator[tuple[Feature, Address]]:
fhi: FunctionContext = fh.inner
bbi: BasicBlockContext = bbh.inner
idx = fhi.ctx.idx
basic_block_index: int = bbi.basic_block_index
target_edges: list[BinExport2.FlowGraph.Edge] = idx.target_edges_by_basic_block_index[basic_block_index]
if basic_block_index in (e.source_basic_block_index for e in target_edges):
basic_block_address: int = idx.get_basic_block_address(basic_block_index)
yield Characteristic("tight loop"), AbsoluteVirtualAddress(basic_block_address)
def extract_features(fh: FunctionHandle, bbh: BBHandle) -> Iterator[tuple[Feature, Address]]:
"""extract basic block features"""
for bb_handler in BASIC_BLOCK_HANDLERS:
for feature, addr in bb_handler(fh, bbh):
yield feature, addr
yield BasicBlock(), bbh.address
BASIC_BLOCK_HANDLERS = (extract_bb_tight_loop,)

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,784 @@
"""
@generated by mypy-protobuf. Do not edit manually!
isort:skip_file
The representation is generic to accommodate various source architectures.
In particular 32 and 64 bit versions of x86, ARM, PowerPC and MIPS have been
tested.
Multiple levels of deduping have been applied to make the format more compact
and avoid redundant data duplication. Some of this due to hard-earned
experience trying to cope with intentionally obfuscated malicious binaries.
Note in particular that the same instruction may occur in multiple basic
blocks and the same basic block in multiple functions (instruction and basic
block sharing). Implemented naively, malware can use this to cause
combinatorial explosion in memory usage, DOSing the analyst. This format
should store every unique expression, mnemonic, operand, instruction and
basic block only once instead of duplicating the information for every
instance of it.
This format does _not_ try to be 100% backwards compatible with the old
version. In particular, we do not store IDA's comment types, making lossless
porting of IDA comments impossible. We do however, store comments and
expression substitutions, so porting the actual data is possible, just not
the exact IDA type.
While it would be more natural to use addresses when defining call graph and
flow graph edges and other such references, it is more efficient to employ
one more level of indirection and use indices into the basic block or
function arrays instead. This is because addresses will usually use most of
the available 64 bit space while indices will be much smaller and compress
much better (less randomly distributed).
We omit all fields that are set to their default value anyways. Note that
this has two side effects:
- changing the defaults in this proto file will, in effect, change what's
read from disk
- the generated code has_* methods are somewhat less useful
WARNING: We omit the defaults manually in the code writing the data. Do not
change the defaults here without changing the code!
TODO(cblichmann): Link flow graphs to call graph nodes. The connection is
there via the address, but tricky to extract.
"""
import builtins
import collections.abc
import google.protobuf.descriptor
import google.protobuf.internal.containers
import google.protobuf.internal.enum_type_wrapper
import google.protobuf.message
import sys
import typing
if sys.version_info >= (3, 10):
import typing as typing_extensions
else:
import typing_extensions
DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
@typing_extensions.final
class BinExport2(google.protobuf.message.Message):
DESCRIPTOR: google.protobuf.descriptor.Descriptor
@typing_extensions.final
class Meta(google.protobuf.message.Message):
DESCRIPTOR: google.protobuf.descriptor.Descriptor
EXECUTABLE_NAME_FIELD_NUMBER: builtins.int
EXECUTABLE_ID_FIELD_NUMBER: builtins.int
ARCHITECTURE_NAME_FIELD_NUMBER: builtins.int
TIMESTAMP_FIELD_NUMBER: builtins.int
executable_name: builtins.str
"""Input binary filename including file extension but excluding file path.
example: "insider_gcc.exe"
"""
executable_id: builtins.str
"""Application defined executable id. Often the SHA256 hash of the input
binary.
"""
architecture_name: builtins.str
"""Input architecture name, e.g. x86-32."""
timestamp: builtins.int
"""When did this file get created? Unix time. This may be used for some
primitive versioning in case the file format ever changes.
"""
def __init__(
self,
*,
executable_name: builtins.str | None = ...,
executable_id: builtins.str | None = ...,
architecture_name: builtins.str | None = ...,
timestamp: builtins.int | None = ...,
) -> None: ...
def HasField(self, field_name: typing_extensions.Literal["architecture_name", b"architecture_name", "executable_id", b"executable_id", "executable_name", b"executable_name", "timestamp", b"timestamp"]) -> builtins.bool: ...
def ClearField(self, field_name: typing_extensions.Literal["architecture_name", b"architecture_name", "executable_id", b"executable_id", "executable_name", b"executable_name", "timestamp", b"timestamp"]) -> None: ...
@typing_extensions.final
class CallGraph(google.protobuf.message.Message):
DESCRIPTOR: google.protobuf.descriptor.Descriptor
@typing_extensions.final
class Vertex(google.protobuf.message.Message):
DESCRIPTOR: google.protobuf.descriptor.Descriptor
class _Type:
ValueType = typing.NewType("ValueType", builtins.int)
V: typing_extensions.TypeAlias = ValueType
class _TypeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[BinExport2.CallGraph.Vertex._Type.ValueType], builtins.type):
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
NORMAL: BinExport2.CallGraph.Vertex._Type.ValueType # 0
"""Regular function with full disassembly."""
LIBRARY: BinExport2.CallGraph.Vertex._Type.ValueType # 1
"""This function is a well known library function."""
IMPORTED: BinExport2.CallGraph.Vertex._Type.ValueType # 2
"""Imported from a dynamic link library (e.g. dll)."""
THUNK: BinExport2.CallGraph.Vertex._Type.ValueType # 3
"""A thunk function, forwarding its work via an unconditional jump."""
INVALID: BinExport2.CallGraph.Vertex._Type.ValueType # 4
"""An invalid function (a function that contained invalid code or was
considered invalid by some heuristics).
"""
class Type(_Type, metaclass=_TypeEnumTypeWrapper): ...
NORMAL: BinExport2.CallGraph.Vertex.Type.ValueType # 0
"""Regular function with full disassembly."""
LIBRARY: BinExport2.CallGraph.Vertex.Type.ValueType # 1
"""This function is a well known library function."""
IMPORTED: BinExport2.CallGraph.Vertex.Type.ValueType # 2
"""Imported from a dynamic link library (e.g. dll)."""
THUNK: BinExport2.CallGraph.Vertex.Type.ValueType # 3
"""A thunk function, forwarding its work via an unconditional jump."""
INVALID: BinExport2.CallGraph.Vertex.Type.ValueType # 4
"""An invalid function (a function that contained invalid code or was
considered invalid by some heuristics).
"""
ADDRESS_FIELD_NUMBER: builtins.int
TYPE_FIELD_NUMBER: builtins.int
MANGLED_NAME_FIELD_NUMBER: builtins.int
DEMANGLED_NAME_FIELD_NUMBER: builtins.int
LIBRARY_INDEX_FIELD_NUMBER: builtins.int
MODULE_INDEX_FIELD_NUMBER: builtins.int
address: builtins.int
"""The function's entry point address. Messages need to be sorted, see
comment below on `vertex`.
"""
type: global___BinExport2.CallGraph.Vertex.Type.ValueType
mangled_name: builtins.str
"""If the function has a user defined, real name it will be given here.
main() is a proper name, sub_BAADF00D is not (auto generated dummy
name).
"""
demangled_name: builtins.str
"""Demangled name if the function is a mangled C++ function and we could
demangle it.
"""
library_index: builtins.int
"""If this is a library function, what is its index in library arrays."""
module_index: builtins.int
"""If module name, such as class name for DEX files, is present - index in
module table.
"""
def __init__(
self,
*,
address: builtins.int | None = ...,
type: global___BinExport2.CallGraph.Vertex.Type.ValueType | None = ...,
mangled_name: builtins.str | None = ...,
demangled_name: builtins.str | None = ...,
library_index: builtins.int | None = ...,
module_index: builtins.int | None = ...,
) -> None: ...
def HasField(self, field_name: typing_extensions.Literal["address", b"address", "demangled_name", b"demangled_name", "library_index", b"library_index", "mangled_name", b"mangled_name", "module_index", b"module_index", "type", b"type"]) -> builtins.bool: ...
def ClearField(self, field_name: typing_extensions.Literal["address", b"address", "demangled_name", b"demangled_name", "library_index", b"library_index", "mangled_name", b"mangled_name", "module_index", b"module_index", "type", b"type"]) -> None: ...
@typing_extensions.final
class Edge(google.protobuf.message.Message):
DESCRIPTOR: google.protobuf.descriptor.Descriptor
SOURCE_VERTEX_INDEX_FIELD_NUMBER: builtins.int
TARGET_VERTEX_INDEX_FIELD_NUMBER: builtins.int
source_vertex_index: builtins.int
"""source and target index into the vertex repeated field."""
target_vertex_index: builtins.int
def __init__(
self,
*,
source_vertex_index: builtins.int | None = ...,
target_vertex_index: builtins.int | None = ...,
) -> None: ...
def HasField(self, field_name: typing_extensions.Literal["source_vertex_index", b"source_vertex_index", "target_vertex_index", b"target_vertex_index"]) -> builtins.bool: ...
def ClearField(self, field_name: typing_extensions.Literal["source_vertex_index", b"source_vertex_index", "target_vertex_index", b"target_vertex_index"]) -> None: ...
VERTEX_FIELD_NUMBER: builtins.int
EDGE_FIELD_NUMBER: builtins.int
@property
def vertex(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___BinExport2.CallGraph.Vertex]:
"""vertices == functions in the call graph.
Important: Most downstream tooling (notably BinDiff), need these to be
sorted by `Vertex::address` (ascending). For C++, the
`BinExport2Writer` class enforces this invariant.
"""
@property
def edge(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___BinExport2.CallGraph.Edge]:
"""edges == calls in the call graph."""
def __init__(
self,
*,
vertex: collections.abc.Iterable[global___BinExport2.CallGraph.Vertex] | None = ...,
edge: collections.abc.Iterable[global___BinExport2.CallGraph.Edge] | None = ...,
) -> None: ...
def ClearField(self, field_name: typing_extensions.Literal["edge", b"edge", "vertex", b"vertex"]) -> None: ...
@typing_extensions.final
class Expression(google.protobuf.message.Message):
"""An operand consists of 1 or more expressions, linked together as a tree."""
DESCRIPTOR: google.protobuf.descriptor.Descriptor
class _Type:
ValueType = typing.NewType("ValueType", builtins.int)
V: typing_extensions.TypeAlias = ValueType
class _TypeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[BinExport2.Expression._Type.ValueType], builtins.type):
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
SYMBOL: BinExport2.Expression._Type.ValueType # 1
IMMEDIATE_INT: BinExport2.Expression._Type.ValueType # 2
IMMEDIATE_FLOAT: BinExport2.Expression._Type.ValueType # 3
OPERATOR: BinExport2.Expression._Type.ValueType # 4
REGISTER: BinExport2.Expression._Type.ValueType # 5
SIZE_PREFIX: BinExport2.Expression._Type.ValueType # 6
DEREFERENCE: BinExport2.Expression._Type.ValueType # 7
class Type(_Type, metaclass=_TypeEnumTypeWrapper): ...
SYMBOL: BinExport2.Expression.Type.ValueType # 1
IMMEDIATE_INT: BinExport2.Expression.Type.ValueType # 2
IMMEDIATE_FLOAT: BinExport2.Expression.Type.ValueType # 3
OPERATOR: BinExport2.Expression.Type.ValueType # 4
REGISTER: BinExport2.Expression.Type.ValueType # 5
SIZE_PREFIX: BinExport2.Expression.Type.ValueType # 6
DEREFERENCE: BinExport2.Expression.Type.ValueType # 7
TYPE_FIELD_NUMBER: builtins.int
SYMBOL_FIELD_NUMBER: builtins.int
IMMEDIATE_FIELD_NUMBER: builtins.int
PARENT_INDEX_FIELD_NUMBER: builtins.int
IS_RELOCATION_FIELD_NUMBER: builtins.int
type: global___BinExport2.Expression.Type.ValueType
"""IMMEDIATE_INT is by far the most common type and thus we can save some
space by omitting it as the default.
"""
symbol: builtins.str
"""Symbol for this expression. Interpretation depends on type. Examples
include: "eax", "[", "+"
"""
immediate: builtins.int
"""If the expression can be interpreted as an integer value (IMMEDIATE_INT)
the value is given here.
"""
parent_index: builtins.int
"""The parent expression. Example expression tree for the second operand of:
mov eax, b4 [ebx + 12]
"b4" --- "[" --- "+" --- "ebx"
\\ "12"
"""
is_relocation: builtins.bool
"""true if the expression has entry in relocation table"""
def __init__(
self,
*,
type: global___BinExport2.Expression.Type.ValueType | None = ...,
symbol: builtins.str | None = ...,
immediate: builtins.int | None = ...,
parent_index: builtins.int | None = ...,
is_relocation: builtins.bool | None = ...,
) -> None: ...
def HasField(self, field_name: typing_extensions.Literal["immediate", b"immediate", "is_relocation", b"is_relocation", "parent_index", b"parent_index", "symbol", b"symbol", "type", b"type"]) -> builtins.bool: ...
def ClearField(self, field_name: typing_extensions.Literal["immediate", b"immediate", "is_relocation", b"is_relocation", "parent_index", b"parent_index", "symbol", b"symbol", "type", b"type"]) -> None: ...
@typing_extensions.final
class Operand(google.protobuf.message.Message):
"""An instruction may have 0 or more operands."""
DESCRIPTOR: google.protobuf.descriptor.Descriptor
EXPRESSION_INDEX_FIELD_NUMBER: builtins.int
@property
def expression_index(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.int]:
"""Contains all expressions constituting this operand. All expressions
should be linked into a single tree, i.e. there should only be one
expression in this list with parent_index == NULL and all others should
descend from that. Rendering order for expressions on the same tree level
(siblings) is implicitly given by the order they are referenced in this
repeated field.
Implicit: expression sequence
"""
def __init__(
self,
*,
expression_index: collections.abc.Iterable[builtins.int] | None = ...,
) -> None: ...
def ClearField(self, field_name: typing_extensions.Literal["expression_index", b"expression_index"]) -> None: ...
@typing_extensions.final
class Mnemonic(google.protobuf.message.Message):
"""An instruction has exactly 1 mnemonic."""
DESCRIPTOR: google.protobuf.descriptor.Descriptor
NAME_FIELD_NUMBER: builtins.int
name: builtins.str
"""Literal representation of the mnemonic, e.g.: "mov"."""
def __init__(
self,
*,
name: builtins.str | None = ...,
) -> None: ...
def HasField(self, field_name: typing_extensions.Literal["name", b"name"]) -> builtins.bool: ...
def ClearField(self, field_name: typing_extensions.Literal["name", b"name"]) -> None: ...
@typing_extensions.final
class Instruction(google.protobuf.message.Message):
DESCRIPTOR: google.protobuf.descriptor.Descriptor
ADDRESS_FIELD_NUMBER: builtins.int
CALL_TARGET_FIELD_NUMBER: builtins.int
MNEMONIC_INDEX_FIELD_NUMBER: builtins.int
OPERAND_INDEX_FIELD_NUMBER: builtins.int
RAW_BYTES_FIELD_NUMBER: builtins.int
COMMENT_INDEX_FIELD_NUMBER: builtins.int
address: builtins.int
"""This will only be filled for instructions that do not just flow from the
immediately preceding instruction. Regular instructions will have to
calculate their own address by adding raw_bytes.size() to the previous
instruction's address.
"""
@property
def call_target(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.int]:
"""If this is a call instruction and call targets could be determined
they'll be given here. Note that we may or may not have a flow graph for
the target and thus cannot use an index into the flow graph table here.
We could potentially use call graph nodes, but linking instructions to
the call graph directly does not seem a good choice.
"""
mnemonic_index: builtins.int
"""Index into the mnemonic array of strings. Used for de-duping the data.
The default value is used for the most common mnemonic in the executable.
"""
@property
def operand_index(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.int]:
"""Indices into the operand tree. On X86 this can be 0, 1 or 2 elements
long, 3 elements with VEX/EVEX.
Implicit: operand sequence
"""
raw_bytes: builtins.bytes
"""The unmodified input bytes corresponding to this instruction."""
@property
def comment_index(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.int]:
"""Implicit: comment sequence"""
def __init__(
self,
*,
address: builtins.int | None = ...,
call_target: collections.abc.Iterable[builtins.int] | None = ...,
mnemonic_index: builtins.int | None = ...,
operand_index: collections.abc.Iterable[builtins.int] | None = ...,
raw_bytes: builtins.bytes | None = ...,
comment_index: collections.abc.Iterable[builtins.int] | None = ...,
) -> None: ...
def HasField(self, field_name: typing_extensions.Literal["address", b"address", "mnemonic_index", b"mnemonic_index", "raw_bytes", b"raw_bytes"]) -> builtins.bool: ...
def ClearField(self, field_name: typing_extensions.Literal["address", b"address", "call_target", b"call_target", "comment_index", b"comment_index", "mnemonic_index", b"mnemonic_index", "operand_index", b"operand_index", "raw_bytes", b"raw_bytes"]) -> None: ...
@typing_extensions.final
class BasicBlock(google.protobuf.message.Message):
DESCRIPTOR: google.protobuf.descriptor.Descriptor
@typing_extensions.final
class IndexRange(google.protobuf.message.Message):
"""This is a space optimization. The instructions for an individual basic
block will usually be in a continuous index range. Thus it is more
efficient to store the range instead of individual indices. However, this
does not hold true for all basic blocks, so we need to be able to store
multiple index ranges per block.
"""
DESCRIPTOR: google.protobuf.descriptor.Descriptor
BEGIN_INDEX_FIELD_NUMBER: builtins.int
END_INDEX_FIELD_NUMBER: builtins.int
begin_index: builtins.int
"""These work like begin and end iterators, i.e. the sequence is
[begin_index, end_index). If the sequence only contains a single
element end_index will be omitted.
"""
end_index: builtins.int
def __init__(
self,
*,
begin_index: builtins.int | None = ...,
end_index: builtins.int | None = ...,
) -> None: ...
def HasField(self, field_name: typing_extensions.Literal["begin_index", b"begin_index", "end_index", b"end_index"]) -> builtins.bool: ...
def ClearField(self, field_name: typing_extensions.Literal["begin_index", b"begin_index", "end_index", b"end_index"]) -> None: ...
INSTRUCTION_INDEX_FIELD_NUMBER: builtins.int
@property
def instruction_index(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___BinExport2.BasicBlock.IndexRange]:
"""Implicit: instruction sequence"""
def __init__(
self,
*,
instruction_index: collections.abc.Iterable[global___BinExport2.BasicBlock.IndexRange] | None = ...,
) -> None: ...
def ClearField(self, field_name: typing_extensions.Literal["instruction_index", b"instruction_index"]) -> None: ...
@typing_extensions.final
class FlowGraph(google.protobuf.message.Message):
DESCRIPTOR: google.protobuf.descriptor.Descriptor
@typing_extensions.final
class Edge(google.protobuf.message.Message):
DESCRIPTOR: google.protobuf.descriptor.Descriptor
class _Type:
ValueType = typing.NewType("ValueType", builtins.int)
V: typing_extensions.TypeAlias = ValueType
class _TypeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[BinExport2.FlowGraph.Edge._Type.ValueType], builtins.type):
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
CONDITION_TRUE: BinExport2.FlowGraph.Edge._Type.ValueType # 1
CONDITION_FALSE: BinExport2.FlowGraph.Edge._Type.ValueType # 2
UNCONDITIONAL: BinExport2.FlowGraph.Edge._Type.ValueType # 3
SWITCH: BinExport2.FlowGraph.Edge._Type.ValueType # 4
class Type(_Type, metaclass=_TypeEnumTypeWrapper): ...
CONDITION_TRUE: BinExport2.FlowGraph.Edge.Type.ValueType # 1
CONDITION_FALSE: BinExport2.FlowGraph.Edge.Type.ValueType # 2
UNCONDITIONAL: BinExport2.FlowGraph.Edge.Type.ValueType # 3
SWITCH: BinExport2.FlowGraph.Edge.Type.ValueType # 4
SOURCE_BASIC_BLOCK_INDEX_FIELD_NUMBER: builtins.int
TARGET_BASIC_BLOCK_INDEX_FIELD_NUMBER: builtins.int
TYPE_FIELD_NUMBER: builtins.int
IS_BACK_EDGE_FIELD_NUMBER: builtins.int
source_basic_block_index: builtins.int
"""Source instruction will always be the last instruction of the source
basic block, target instruction the first instruction of the target
basic block.
"""
target_basic_block_index: builtins.int
type: global___BinExport2.FlowGraph.Edge.Type.ValueType
is_back_edge: builtins.bool
"""Indicates whether this is a loop edge as determined by Lengauer-Tarjan."""
def __init__(
self,
*,
source_basic_block_index: builtins.int | None = ...,
target_basic_block_index: builtins.int | None = ...,
type: global___BinExport2.FlowGraph.Edge.Type.ValueType | None = ...,
is_back_edge: builtins.bool | None = ...,
) -> None: ...
def HasField(self, field_name: typing_extensions.Literal["is_back_edge", b"is_back_edge", "source_basic_block_index", b"source_basic_block_index", "target_basic_block_index", b"target_basic_block_index", "type", b"type"]) -> builtins.bool: ...
def ClearField(self, field_name: typing_extensions.Literal["is_back_edge", b"is_back_edge", "source_basic_block_index", b"source_basic_block_index", "target_basic_block_index", b"target_basic_block_index", "type", b"type"]) -> None: ...
BASIC_BLOCK_INDEX_FIELD_NUMBER: builtins.int
ENTRY_BASIC_BLOCK_INDEX_FIELD_NUMBER: builtins.int
EDGE_FIELD_NUMBER: builtins.int
@property
def basic_block_index(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.int]:
"""Basic blocks are sorted by address."""
entry_basic_block_index: builtins.int
"""The flow graph's entry point address is the first instruction of the
entry_basic_block.
"""
@property
def edge(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___BinExport2.FlowGraph.Edge]: ...
def __init__(
self,
*,
basic_block_index: collections.abc.Iterable[builtins.int] | None = ...,
entry_basic_block_index: builtins.int | None = ...,
edge: collections.abc.Iterable[global___BinExport2.FlowGraph.Edge] | None = ...,
) -> None: ...
def HasField(self, field_name: typing_extensions.Literal["entry_basic_block_index", b"entry_basic_block_index"]) -> builtins.bool: ...
def ClearField(self, field_name: typing_extensions.Literal["basic_block_index", b"basic_block_index", "edge", b"edge", "entry_basic_block_index", b"entry_basic_block_index"]) -> None: ...
@typing_extensions.final
class Reference(google.protobuf.message.Message):
"""Generic reference class used for address comments (deprecated), string
references and expression substitutions. It allows referencing from an
instruction, operand, expression subtree tuple to a de-duped string in the
string table.
"""
DESCRIPTOR: google.protobuf.descriptor.Descriptor
INSTRUCTION_INDEX_FIELD_NUMBER: builtins.int
INSTRUCTION_OPERAND_INDEX_FIELD_NUMBER: builtins.int
OPERAND_EXPRESSION_INDEX_FIELD_NUMBER: builtins.int
STRING_TABLE_INDEX_FIELD_NUMBER: builtins.int
instruction_index: builtins.int
"""Index into the global instruction table."""
instruction_operand_index: builtins.int
"""Index into the operand array local to an instruction."""
operand_expression_index: builtins.int
"""Index into the expression array local to an operand."""
string_table_index: builtins.int
"""Index into the global string table."""
def __init__(
self,
*,
instruction_index: builtins.int | None = ...,
instruction_operand_index: builtins.int | None = ...,
operand_expression_index: builtins.int | None = ...,
string_table_index: builtins.int | None = ...,
) -> None: ...
def HasField(self, field_name: typing_extensions.Literal["instruction_index", b"instruction_index", "instruction_operand_index", b"instruction_operand_index", "operand_expression_index", b"operand_expression_index", "string_table_index", b"string_table_index"]) -> builtins.bool: ...
def ClearField(self, field_name: typing_extensions.Literal["instruction_index", b"instruction_index", "instruction_operand_index", b"instruction_operand_index", "operand_expression_index", b"operand_expression_index", "string_table_index", b"string_table_index"]) -> None: ...
@typing_extensions.final
class DataReference(google.protobuf.message.Message):
DESCRIPTOR: google.protobuf.descriptor.Descriptor
INSTRUCTION_INDEX_FIELD_NUMBER: builtins.int
ADDRESS_FIELD_NUMBER: builtins.int
instruction_index: builtins.int
"""Index into the global instruction table."""
address: builtins.int
"""Address being referred."""
def __init__(
self,
*,
instruction_index: builtins.int | None = ...,
address: builtins.int | None = ...,
) -> None: ...
def HasField(self, field_name: typing_extensions.Literal["address", b"address", "instruction_index", b"instruction_index"]) -> builtins.bool: ...
def ClearField(self, field_name: typing_extensions.Literal["address", b"address", "instruction_index", b"instruction_index"]) -> None: ...
@typing_extensions.final
class Comment(google.protobuf.message.Message):
DESCRIPTOR: google.protobuf.descriptor.Descriptor
class _Type:
ValueType = typing.NewType("ValueType", builtins.int)
V: typing_extensions.TypeAlias = ValueType
class _TypeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[BinExport2.Comment._Type.ValueType], builtins.type):
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
DEFAULT: BinExport2.Comment._Type.ValueType # 0
"""A regular instruction comment. Typically displayed next to the
instruction disassembly.
"""
ANTERIOR: BinExport2.Comment._Type.ValueType # 1
"""A comment line that is typically displayed before (above) the
instruction it refers to.
"""
POSTERIOR: BinExport2.Comment._Type.ValueType # 2
"""Like ANTERIOR, but a typically displayed after (below)."""
FUNCTION: BinExport2.Comment._Type.ValueType # 3
"""Similar to an ANTERIOR comment, but applies to the beginning of an
identified function. Programs displaying the proto may choose to render
these differently (e.g. above an inferred function signature).
"""
ENUM: BinExport2.Comment._Type.ValueType # 4
"""Named constants, bitfields and similar."""
LOCATION: BinExport2.Comment._Type.ValueType # 5
"""Named locations, usually the target of a jump."""
GLOBAL_REFERENCE: BinExport2.Comment._Type.ValueType # 6
"""Data cross references."""
LOCAL_REFERENCE: BinExport2.Comment._Type.ValueType # 7
"""Local/stack variables."""
class Type(_Type, metaclass=_TypeEnumTypeWrapper): ...
DEFAULT: BinExport2.Comment.Type.ValueType # 0
"""A regular instruction comment. Typically displayed next to the
instruction disassembly.
"""
ANTERIOR: BinExport2.Comment.Type.ValueType # 1
"""A comment line that is typically displayed before (above) the
instruction it refers to.
"""
POSTERIOR: BinExport2.Comment.Type.ValueType # 2
"""Like ANTERIOR, but a typically displayed after (below)."""
FUNCTION: BinExport2.Comment.Type.ValueType # 3
"""Similar to an ANTERIOR comment, but applies to the beginning of an
identified function. Programs displaying the proto may choose to render
these differently (e.g. above an inferred function signature).
"""
ENUM: BinExport2.Comment.Type.ValueType # 4
"""Named constants, bitfields and similar."""
LOCATION: BinExport2.Comment.Type.ValueType # 5
"""Named locations, usually the target of a jump."""
GLOBAL_REFERENCE: BinExport2.Comment.Type.ValueType # 6
"""Data cross references."""
LOCAL_REFERENCE: BinExport2.Comment.Type.ValueType # 7
"""Local/stack variables."""
INSTRUCTION_INDEX_FIELD_NUMBER: builtins.int
INSTRUCTION_OPERAND_INDEX_FIELD_NUMBER: builtins.int
OPERAND_EXPRESSION_INDEX_FIELD_NUMBER: builtins.int
STRING_TABLE_INDEX_FIELD_NUMBER: builtins.int
REPEATABLE_FIELD_NUMBER: builtins.int
TYPE_FIELD_NUMBER: builtins.int
instruction_index: builtins.int
"""Index into the global instruction table. This is here to enable
comment processing without having to iterate over all instructions.
There is an N:M mapping of instructions to comments.
"""
instruction_operand_index: builtins.int
"""Index into the operand array local to an instruction."""
operand_expression_index: builtins.int
"""Index into the expression array local to an operand, like in Reference.
This is not currently used, but allows to implement expression
substitutions.
"""
string_table_index: builtins.int
"""Index into the global string table."""
repeatable: builtins.bool
"""Comment is propagated to all locations that reference the original
location.
"""
type: global___BinExport2.Comment.Type.ValueType
def __init__(
self,
*,
instruction_index: builtins.int | None = ...,
instruction_operand_index: builtins.int | None = ...,
operand_expression_index: builtins.int | None = ...,
string_table_index: builtins.int | None = ...,
repeatable: builtins.bool | None = ...,
type: global___BinExport2.Comment.Type.ValueType | None = ...,
) -> None: ...
def HasField(self, field_name: typing_extensions.Literal["instruction_index", b"instruction_index", "instruction_operand_index", b"instruction_operand_index", "operand_expression_index", b"operand_expression_index", "repeatable", b"repeatable", "string_table_index", b"string_table_index", "type", b"type"]) -> builtins.bool: ...
def ClearField(self, field_name: typing_extensions.Literal["instruction_index", b"instruction_index", "instruction_operand_index", b"instruction_operand_index", "operand_expression_index", b"operand_expression_index", "repeatable", b"repeatable", "string_table_index", b"string_table_index", "type", b"type"]) -> None: ...
@typing_extensions.final
class Section(google.protobuf.message.Message):
DESCRIPTOR: google.protobuf.descriptor.Descriptor
ADDRESS_FIELD_NUMBER: builtins.int
SIZE_FIELD_NUMBER: builtins.int
FLAG_R_FIELD_NUMBER: builtins.int
FLAG_W_FIELD_NUMBER: builtins.int
FLAG_X_FIELD_NUMBER: builtins.int
address: builtins.int
"""Section start address."""
size: builtins.int
"""Section size."""
flag_r: builtins.bool
"""Read flag of the section, True when section is readable."""
flag_w: builtins.bool
"""Write flag of the section, True when section is writable."""
flag_x: builtins.bool
"""Execute flag of the section, True when section is executable."""
def __init__(
self,
*,
address: builtins.int | None = ...,
size: builtins.int | None = ...,
flag_r: builtins.bool | None = ...,
flag_w: builtins.bool | None = ...,
flag_x: builtins.bool | None = ...,
) -> None: ...
def HasField(self, field_name: typing_extensions.Literal["address", b"address", "flag_r", b"flag_r", "flag_w", b"flag_w", "flag_x", b"flag_x", "size", b"size"]) -> builtins.bool: ...
def ClearField(self, field_name: typing_extensions.Literal["address", b"address", "flag_r", b"flag_r", "flag_w", b"flag_w", "flag_x", b"flag_x", "size", b"size"]) -> None: ...
@typing_extensions.final
class Library(google.protobuf.message.Message):
DESCRIPTOR: google.protobuf.descriptor.Descriptor
IS_STATIC_FIELD_NUMBER: builtins.int
LOAD_ADDRESS_FIELD_NUMBER: builtins.int
NAME_FIELD_NUMBER: builtins.int
is_static: builtins.bool
"""If this library is statically linked."""
load_address: builtins.int
"""Address where this library was loaded, 0 if unknown."""
name: builtins.str
"""Name of the library (format is platform-dependent)."""
def __init__(
self,
*,
is_static: builtins.bool | None = ...,
load_address: builtins.int | None = ...,
name: builtins.str | None = ...,
) -> None: ...
def HasField(self, field_name: typing_extensions.Literal["is_static", b"is_static", "load_address", b"load_address", "name", b"name"]) -> builtins.bool: ...
def ClearField(self, field_name: typing_extensions.Literal["is_static", b"is_static", "load_address", b"load_address", "name", b"name"]) -> None: ...
@typing_extensions.final
class Module(google.protobuf.message.Message):
DESCRIPTOR: google.protobuf.descriptor.Descriptor
NAME_FIELD_NUMBER: builtins.int
name: builtins.str
"""Name, such as Java class name. Platform-dependent."""
def __init__(
self,
*,
name: builtins.str | None = ...,
) -> None: ...
def HasField(self, field_name: typing_extensions.Literal["name", b"name"]) -> builtins.bool: ...
def ClearField(self, field_name: typing_extensions.Literal["name", b"name"]) -> None: ...
META_INFORMATION_FIELD_NUMBER: builtins.int
EXPRESSION_FIELD_NUMBER: builtins.int
OPERAND_FIELD_NUMBER: builtins.int
MNEMONIC_FIELD_NUMBER: builtins.int
INSTRUCTION_FIELD_NUMBER: builtins.int
BASIC_BLOCK_FIELD_NUMBER: builtins.int
FLOW_GRAPH_FIELD_NUMBER: builtins.int
CALL_GRAPH_FIELD_NUMBER: builtins.int
STRING_TABLE_FIELD_NUMBER: builtins.int
ADDRESS_COMMENT_FIELD_NUMBER: builtins.int
COMMENT_FIELD_NUMBER: builtins.int
STRING_REFERENCE_FIELD_NUMBER: builtins.int
EXPRESSION_SUBSTITUTION_FIELD_NUMBER: builtins.int
SECTION_FIELD_NUMBER: builtins.int
LIBRARY_FIELD_NUMBER: builtins.int
DATA_REFERENCE_FIELD_NUMBER: builtins.int
MODULE_FIELD_NUMBER: builtins.int
@property
def meta_information(self) -> global___BinExport2.Meta: ...
@property
def expression(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___BinExport2.Expression]: ...
@property
def operand(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___BinExport2.Operand]: ...
@property
def mnemonic(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___BinExport2.Mnemonic]: ...
@property
def instruction(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___BinExport2.Instruction]: ...
@property
def basic_block(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___BinExport2.BasicBlock]: ...
@property
def flow_graph(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___BinExport2.FlowGraph]: ...
@property
def call_graph(self) -> global___BinExport2.CallGraph: ...
@property
def string_table(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: ...
@property
def address_comment(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___BinExport2.Reference]:
"""No longer written. This is here so that BinDiff can work with older
BinExport files.
"""
@property
def comment(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___BinExport2.Comment]:
"""Rich comment index used for BinDiff's comment porting."""
@property
def string_reference(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___BinExport2.Reference]: ...
@property
def expression_substitution(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___BinExport2.Reference]: ...
@property
def section(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___BinExport2.Section]: ...
@property
def library(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___BinExport2.Library]: ...
@property
def data_reference(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___BinExport2.DataReference]: ...
@property
def module(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___BinExport2.Module]: ...
def __init__(
self,
*,
meta_information: global___BinExport2.Meta | None = ...,
expression: collections.abc.Iterable[global___BinExport2.Expression] | None = ...,
operand: collections.abc.Iterable[global___BinExport2.Operand] | None = ...,
mnemonic: collections.abc.Iterable[global___BinExport2.Mnemonic] | None = ...,
instruction: collections.abc.Iterable[global___BinExport2.Instruction] | None = ...,
basic_block: collections.abc.Iterable[global___BinExport2.BasicBlock] | None = ...,
flow_graph: collections.abc.Iterable[global___BinExport2.FlowGraph] | None = ...,
call_graph: global___BinExport2.CallGraph | None = ...,
string_table: collections.abc.Iterable[builtins.str] | None = ...,
address_comment: collections.abc.Iterable[global___BinExport2.Reference] | None = ...,
comment: collections.abc.Iterable[global___BinExport2.Comment] | None = ...,
string_reference: collections.abc.Iterable[global___BinExport2.Reference] | None = ...,
expression_substitution: collections.abc.Iterable[global___BinExport2.Reference] | None = ...,
section: collections.abc.Iterable[global___BinExport2.Section] | None = ...,
library: collections.abc.Iterable[global___BinExport2.Library] | None = ...,
data_reference: collections.abc.Iterable[global___BinExport2.DataReference] | None = ...,
module: collections.abc.Iterable[global___BinExport2.Module] | None = ...,
) -> None: ...
def HasField(self, field_name: typing_extensions.Literal["call_graph", b"call_graph", "meta_information", b"meta_information"]) -> builtins.bool: ...
def ClearField(self, field_name: typing_extensions.Literal["address_comment", b"address_comment", "basic_block", b"basic_block", "call_graph", b"call_graph", "comment", b"comment", "data_reference", b"data_reference", "expression", b"expression", "expression_substitution", b"expression_substitution", "flow_graph", b"flow_graph", "instruction", b"instruction", "library", b"library", "meta_information", b"meta_information", "mnemonic", b"mnemonic", "module", b"module", "operand", b"operand", "section", b"section", "string_reference", b"string_reference", "string_table", b"string_table"]) -> None: ...
global___BinExport2 = BinExport2

View File

@@ -0,0 +1,130 @@
# Copyright (C) 2023 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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.
import logging
from typing import Iterator
import capa.features.extractors.elf
import capa.features.extractors.common
import capa.features.extractors.binexport2.file
import capa.features.extractors.binexport2.insn
import capa.features.extractors.binexport2.helpers
import capa.features.extractors.binexport2.function
import capa.features.extractors.binexport2.basicblock
from capa.features.common import OS, Arch, Format, Feature
from capa.features.address import Address, AbsoluteVirtualAddress
from capa.features.extractors.binexport2 import (
AddressSpace,
AnalysisContext,
BinExport2Index,
FunctionContext,
BasicBlockContext,
BinExport2Analysis,
InstructionContext,
)
from capa.features.extractors.base_extractor import (
BBHandle,
InsnHandle,
SampleHashes,
FunctionHandle,
StaticFeatureExtractor,
)
from capa.features.extractors.binexport2.binexport2_pb2 import BinExport2
logger = logging.getLogger(__name__)
class BinExport2FeatureExtractor(StaticFeatureExtractor):
def __init__(self, be2: BinExport2, buf: bytes):
super().__init__(hashes=SampleHashes.from_bytes(buf))
self.be2: BinExport2 = be2
self.buf: bytes = buf
self.idx: BinExport2Index = BinExport2Index(self.be2)
self.analysis: BinExport2Analysis = BinExport2Analysis(self.be2, self.idx, self.buf)
address_space: AddressSpace = AddressSpace.from_buf(buf, self.analysis.base_address)
self.ctx: AnalysisContext = AnalysisContext(self.buf, self.be2, self.idx, self.analysis, address_space)
self.global_features: list[tuple[Feature, Address]] = []
self.global_features.extend(list(capa.features.extractors.common.extract_format(self.buf)))
self.global_features.extend(list(capa.features.extractors.common.extract_os(self.buf)))
self.global_features.extend(list(capa.features.extractors.common.extract_arch(self.buf)))
self.format: set[str] = set()
self.os: set[str] = set()
self.arch: set[str] = set()
for feature, _ in self.global_features:
assert isinstance(feature.value, str)
if isinstance(feature, Format):
self.format.add(feature.value)
elif isinstance(feature, OS):
self.os.add(feature.value)
elif isinstance(feature, Arch):
self.arch.add(feature.value)
else:
raise ValueError("unexpected global feature: %s", feature)
def get_base_address(self) -> AbsoluteVirtualAddress:
return AbsoluteVirtualAddress(self.analysis.base_address)
def extract_global_features(self) -> Iterator[tuple[Feature, Address]]:
yield from self.global_features
def extract_file_features(self) -> Iterator[tuple[Feature, Address]]:
yield from capa.features.extractors.binexport2.file.extract_features(self.be2, self.buf)
def get_functions(self) -> Iterator[FunctionHandle]:
for flow_graph_index, flow_graph in enumerate(self.be2.flow_graph):
entry_basic_block_index: int = flow_graph.entry_basic_block_index
flow_graph_address: int = self.idx.get_basic_block_address(entry_basic_block_index)
vertex_idx: int = self.idx.vertex_index_by_address[flow_graph_address]
be2_vertex: BinExport2.CallGraph.Vertex = self.be2.call_graph.vertex[vertex_idx]
# skip thunks
if capa.features.extractors.binexport2.helpers.is_vertex_type(
be2_vertex, BinExport2.CallGraph.Vertex.Type.THUNK
):
continue
yield FunctionHandle(
AbsoluteVirtualAddress(flow_graph_address),
inner=FunctionContext(self.ctx, flow_graph_index, self.format, self.os, self.arch),
)
def extract_function_features(self, fh: FunctionHandle) -> Iterator[tuple[Feature, Address]]:
yield from capa.features.extractors.binexport2.function.extract_features(fh)
def get_basic_blocks(self, fh: FunctionHandle) -> Iterator[BBHandle]:
fhi: FunctionContext = fh.inner
flow_graph_index: int = fhi.flow_graph_index
flow_graph: BinExport2.FlowGraph = self.be2.flow_graph[flow_graph_index]
for basic_block_index in flow_graph.basic_block_index:
basic_block_address: int = self.idx.get_basic_block_address(basic_block_index)
yield BBHandle(
address=AbsoluteVirtualAddress(basic_block_address),
inner=BasicBlockContext(basic_block_index),
)
def extract_basic_block_features(self, fh: FunctionHandle, bbh: BBHandle) -> Iterator[tuple[Feature, Address]]:
yield from capa.features.extractors.binexport2.basicblock.extract_features(fh, bbh)
def get_instructions(self, fh: FunctionHandle, bbh: BBHandle) -> Iterator[InsnHandle]:
bbi: BasicBlockContext = bbh.inner
basic_block: BinExport2.BasicBlock = self.be2.basic_block[bbi.basic_block_index]
for instruction_index, _, instruction_address in self.idx.basic_block_instructions(basic_block):
yield InsnHandle(
address=AbsoluteVirtualAddress(instruction_address),
inner=InstructionContext(instruction_index),
)
def extract_insn_features(
self, fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle
) -> Iterator[tuple[Feature, Address]]:
yield from capa.features.extractors.binexport2.insn.extract_features(fh, bbh, ih)

View File

@@ -0,0 +1,80 @@
# Copyright (C) 2023 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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.
import io
import logging
from typing import Iterator
import pefile
from elftools.elf.elffile import ELFFile
import capa.features.common
import capa.features.extractors.common
import capa.features.extractors.pefile
import capa.features.extractors.elffile
from capa.features.common import Feature
from capa.features.address import Address
from capa.features.extractors.binexport2.binexport2_pb2 import BinExport2
logger = logging.getLogger(__name__)
def extract_file_export_names(_be2: BinExport2, buf: bytes) -> Iterator[tuple[Feature, Address]]:
if buf.startswith(capa.features.extractors.common.MATCH_PE):
pe: pefile.PE = pefile.PE(data=buf)
yield from capa.features.extractors.pefile.extract_file_export_names(pe)
elif buf.startswith(capa.features.extractors.common.MATCH_ELF):
elf: ELFFile = ELFFile(io.BytesIO(buf))
yield from capa.features.extractors.elffile.extract_file_export_names(elf)
else:
logger.warning("unsupported format")
def extract_file_import_names(_be2: BinExport2, buf: bytes) -> Iterator[tuple[Feature, Address]]:
if buf.startswith(capa.features.extractors.common.MATCH_PE):
pe: pefile.PE = pefile.PE(data=buf)
yield from capa.features.extractors.pefile.extract_file_import_names(pe)
elif buf.startswith(capa.features.extractors.common.MATCH_ELF):
elf: ELFFile = ELFFile(io.BytesIO(buf))
yield from capa.features.extractors.elffile.extract_file_import_names(elf)
else:
logger.warning("unsupported format")
def extract_file_section_names(_be2: BinExport2, buf: bytes) -> Iterator[tuple[Feature, Address]]:
if buf.startswith(capa.features.extractors.common.MATCH_PE):
pe: pefile.PE = pefile.PE(data=buf)
yield from capa.features.extractors.pefile.extract_file_section_names(pe)
elif buf.startswith(capa.features.extractors.common.MATCH_ELF):
elf: ELFFile = ELFFile(io.BytesIO(buf))
yield from capa.features.extractors.elffile.extract_file_section_names(elf)
else:
logger.warning("unsupported format")
def extract_file_strings(_be2: BinExport2, buf: bytes) -> Iterator[tuple[Feature, Address]]:
yield from capa.features.extractors.common.extract_file_strings(buf)
def extract_file_format(_be2: BinExport2, buf: bytes) -> Iterator[tuple[Feature, Address]]:
yield from capa.features.extractors.common.extract_format(buf)
def extract_features(be2: BinExport2, buf: bytes) -> Iterator[tuple[Feature, Address]]:
"""extract file features"""
for file_handler in FILE_HANDLERS:
for feature, addr in file_handler(be2, buf):
yield feature, addr
FILE_HANDLERS = (
extract_file_export_names,
extract_file_import_names,
extract_file_strings,
extract_file_section_names,
extract_file_format,
)

View File

@@ -0,0 +1,72 @@
# Copyright (C) 2023 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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.
from typing import Iterator
from capa.features.file import FunctionName
from capa.features.common import Feature, Characteristic
from capa.features.address import Address, AbsoluteVirtualAddress
from capa.features.extractors import loops
from capa.features.extractors.binexport2 import BinExport2Index, FunctionContext
from capa.features.extractors.base_extractor import FunctionHandle
from capa.features.extractors.binexport2.binexport2_pb2 import BinExport2
def extract_function_calls_to(fh: FunctionHandle) -> Iterator[tuple[Feature, Address]]:
fhi: FunctionContext = fh.inner
be2: BinExport2 = fhi.ctx.be2
idx: BinExport2Index = fhi.ctx.idx
flow_graph_index: int = fhi.flow_graph_index
flow_graph_address: int = idx.flow_graph_address_by_index[flow_graph_index]
vertex_index: int = idx.vertex_index_by_address[flow_graph_address]
for caller_index in idx.callers_by_vertex_index[vertex_index]:
caller: BinExport2.CallGraph.Vertex = be2.call_graph.vertex[caller_index]
caller_address: int = caller.address
yield Characteristic("calls to"), AbsoluteVirtualAddress(caller_address)
def extract_function_loop(fh: FunctionHandle) -> Iterator[tuple[Feature, Address]]:
fhi: FunctionContext = fh.inner
be2: BinExport2 = fhi.ctx.be2
flow_graph_index: int = fhi.flow_graph_index
flow_graph: BinExport2.FlowGraph = be2.flow_graph[flow_graph_index]
edges: list[tuple[int, int]] = []
for edge in flow_graph.edge:
edges.append((edge.source_basic_block_index, edge.target_basic_block_index))
if loops.has_loop(edges):
yield Characteristic("loop"), fh.address
def extract_function_name(fh: FunctionHandle) -> Iterator[tuple[Feature, Address]]:
fhi: FunctionContext = fh.inner
be2: BinExport2 = fhi.ctx.be2
idx: BinExport2Index = fhi.ctx.idx
flow_graph_index: int = fhi.flow_graph_index
flow_graph_address: int = idx.flow_graph_address_by_index[flow_graph_index]
vertex_index: int = idx.vertex_index_by_address[flow_graph_address]
vertex: BinExport2.CallGraph.Vertex = be2.call_graph.vertex[vertex_index]
if vertex.HasField("mangled_name"):
yield FunctionName(vertex.mangled_name), fh.address
def extract_features(fh: FunctionHandle) -> Iterator[tuple[Feature, Address]]:
for func_handler in FUNCTION_HANDLERS:
for feature, addr in func_handler(fh):
yield feature, addr
FUNCTION_HANDLERS = (extract_function_calls_to, extract_function_loop, extract_function_name)

View File

@@ -0,0 +1,692 @@
# Copyright (C) 2024 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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.
import re
from typing import Union, Iterator, Optional
from collections import defaultdict
from dataclasses import dataclass
import capa.features.extractors.helpers
import capa.features.extractors.binexport2.helpers
from capa.features.common import ARCH_I386, ARCH_AMD64, ARCH_AARCH64
from capa.features.extractors.binexport2.binexport2_pb2 import BinExport2
HAS_ARCH32 = {ARCH_I386}
HAS_ARCH64 = {ARCH_AARCH64, ARCH_AMD64}
HAS_ARCH_INTEL = {ARCH_I386, ARCH_AMD64}
HAS_ARCH_ARM = {ARCH_AARCH64}
def mask_immediate(arch: set[str], immediate: int) -> int:
if arch & HAS_ARCH64:
immediate &= 0xFFFFFFFFFFFFFFFF
elif arch & HAS_ARCH32:
immediate &= 0xFFFFFFFF
return immediate
def twos_complement(arch: set[str], immediate: int, default: Optional[int] = None) -> int:
if default is not None:
return capa.features.extractors.helpers.twos_complement(immediate, default)
elif arch & HAS_ARCH64:
return capa.features.extractors.helpers.twos_complement(immediate, 64)
elif arch & HAS_ARCH32:
return capa.features.extractors.helpers.twos_complement(immediate, 32)
return immediate
def is_address_mapped(be2: BinExport2, address: int) -> bool:
"""return True if the given address is mapped"""
sections_with_perms: Iterator[BinExport2.Section] = filter(lambda s: s.flag_r or s.flag_w or s.flag_x, be2.section)
return any(section.address <= address < section.address + section.size for section in sections_with_perms)
def is_vertex_type(vertex: BinExport2.CallGraph.Vertex, type_: BinExport2.CallGraph.Vertex.Type.ValueType) -> bool:
return vertex.HasField("type") and vertex.type == type_
# internal to `build_expression_tree`
# this is unstable: it is subject to change, so don't rely on it!
def _prune_expression_tree_references_to_tree_index(
expression_tree: list[list[int]],
tree_index: int,
):
# `i` is the index of the tree node that we'll search for `tree_index`
# if we remove `tree_index` from it, and it is now empty,
# then we'll need to prune references to `i`.
for i, tree_node in enumerate(expression_tree):
if tree_index in tree_node:
tree_node.remove(tree_index)
if len(tree_node) == 0:
# if the parent node is now empty,
# remove references to that parent node.
_prune_expression_tree_references_to_tree_index(expression_tree, i)
# internal to `build_expression_tree`
# this is unstable: it is subject to change, so don't rely on it!
def _prune_expression_tree_empty_shifts(
be2: BinExport2,
operand: BinExport2.Operand,
expression_tree: list[list[int]],
tree_index: int,
):
expression_index = operand.expression_index[tree_index]
expression = be2.expression[expression_index]
children_tree_indexes: list[int] = expression_tree[tree_index]
if expression.type == BinExport2.Expression.OPERATOR:
if len(children_tree_indexes) == 0 and expression.symbol in ("lsl", "lsr"):
# Ghidra may emit superfluous lsl nodes with no children.
# https://github.com/mandiant/capa/pull/2340/files#r1750003919
# Which is maybe: https://github.com/NationalSecurityAgency/ghidra/issues/6821#issuecomment-2295394697
#
# Which seems to be as if the shift wasn't there (shift of #0)
# so we want to remove references to this node from any parent nodes.
_prune_expression_tree_references_to_tree_index(expression_tree, tree_index)
return
for child_tree_index in children_tree_indexes:
_prune_expression_tree_empty_shifts(be2, operand, expression_tree, child_tree_index)
# internal to `build_expression_tree`
# this is unstable: it is subject to change, so don't rely on it!
def _fixup_expression_tree_references_to_tree_index(
expression_tree: list[list[int]],
existing_index: int,
new_index: int,
):
for tree_node in expression_tree:
for i, index in enumerate(tree_node):
if index == existing_index:
tree_node[i] = new_index
# internal to `build_expression_tree`
# this is unstable: it is subject to change, so don't rely on it!
def _fixup_expression_tree_lonely_commas(
be2: BinExport2,
operand: BinExport2.Operand,
expression_tree: list[list[int]],
tree_index: int,
):
expression_index = operand.expression_index[tree_index]
expression = be2.expression[expression_index]
children_tree_indexes: list[int] = expression_tree[tree_index]
if expression.type == BinExport2.Expression.OPERATOR:
if len(children_tree_indexes) == 1 and expression.symbol == ",":
existing_index = tree_index
new_index = children_tree_indexes[0]
_fixup_expression_tree_references_to_tree_index(expression_tree, existing_index, new_index)
for child_tree_index in children_tree_indexes:
_fixup_expression_tree_lonely_commas(be2, operand, expression_tree, child_tree_index)
# internal to `build_expression_tree`
# this is unstable: it is subject to change, so don't rely on it!
def _prune_expression_tree(
be2: BinExport2,
operand: BinExport2.Operand,
expression_tree: list[list[int]],
):
_prune_expression_tree_empty_shifts(be2, operand, expression_tree, 0)
_fixup_expression_tree_lonely_commas(be2, operand, expression_tree, 0)
# this is unstable: it is subject to change, so don't rely on it!
def _build_expression_tree(
be2: BinExport2,
operand: BinExport2.Operand,
) -> list[list[int]]:
# The reconstructed expression tree layout, linking parent nodes to their children.
#
# There is one list of integers for each expression in the operand.
# These integers are indexes of other expressions in the same operand,
# which are the children of that expression.
#
# So:
#
# [ [1, 3], [2], [], [4], [5], []]
#
# means the first expression has two children, at index 1 and 3,
# and the tree looks like:
#
# 0
# / \
# 1 3
# | |
# 2 4
# |
# 5
#
# Remember, these are the indices into the entries in operand.expression_index.
if len(operand.expression_index) == 0:
# Ghidra bug where empty operands (no expressions) may
# exist (see https://github.com/NationalSecurityAgency/ghidra/issues/6817)
return []
tree: list[list[int]] = []
for i, expression_index in enumerate(operand.expression_index):
children = []
# scan all subsequent expressions, looking for those that have parent_index == current.expression_index
for j, candidate_index in enumerate(operand.expression_index[i + 1 :]):
candidate = be2.expression[candidate_index]
if candidate.parent_index == expression_index:
children.append(i + j + 1)
tree.append(children)
_prune_expression_tree(be2, operand, tree)
return tree
def _fill_operand_expression_list(
be2: BinExport2,
operand: BinExport2.Operand,
expression_tree: list[list[int]],
tree_index: int,
expression_list: list[BinExport2.Expression],
):
"""
Walk the given expression tree and collect the expression nodes in-order.
"""
expression_index = operand.expression_index[tree_index]
expression = be2.expression[expression_index]
children_tree_indexes: list[int] = expression_tree[tree_index]
if expression.type == BinExport2.Expression.REGISTER:
assert len(children_tree_indexes) <= 1
expression_list.append(expression)
if len(children_tree_indexes) == 0:
return
elif len(children_tree_indexes) == 1:
# like for aarch64 with vector instructions, indicating vector data size:
#
# FADD V0.4S, V1.4S, V2.4S
#
# see: https://github.com/mandiant/capa/issues/2528
child_index = children_tree_indexes[0]
_fill_operand_expression_list(be2, operand, expression_tree, child_index, expression_list)
return
else:
raise NotImplementedError(len(children_tree_indexes))
elif expression.type == BinExport2.Expression.SYMBOL:
assert len(children_tree_indexes) <= 1
expression_list.append(expression)
if len(children_tree_indexes) == 0:
return
elif len(children_tree_indexes) == 1:
# like: v
# from: mov v0.D[0x1], x9
# |
# 0
# .
# |
# D
child_index = children_tree_indexes[0]
_fill_operand_expression_list(be2, operand, expression_tree, child_index, expression_list)
return
else:
raise NotImplementedError(len(children_tree_indexes))
elif expression.type == BinExport2.Expression.IMMEDIATE_INT:
assert len(children_tree_indexes) <= 1
expression_list.append(expression)
if len(children_tree_indexes) == 0:
return
elif len(children_tree_indexes) == 1:
# the ghidra exporter can produce some weird expressions,
# particularly for MSRs, like for:
#
# sreg(3, 0, c.0, c.4, 4)
#
# see: https://github.com/mandiant/capa/issues/2530
child_index = children_tree_indexes[0]
_fill_operand_expression_list(be2, operand, expression_tree, child_index, expression_list)
return
else:
raise NotImplementedError(len(children_tree_indexes))
elif expression.type == BinExport2.Expression.SIZE_PREFIX:
# like: b4
#
# We might want to use this occasionally, such as to disambiguate the
# size of MOVs into/out of memory. But I'm not sure when/where we need that yet.
#
# IDA spams this size prefix hint *everywhere*, so we can't rely on the exporter
# to provide it only when necessary.
assert len(children_tree_indexes) == 1
child_index = children_tree_indexes[0]
_fill_operand_expression_list(be2, operand, expression_tree, child_index, expression_list)
return
elif expression.type == BinExport2.Expression.OPERATOR:
if len(children_tree_indexes) == 1:
# prefix operator, like "ds:"
expression_list.append(expression)
child_index = children_tree_indexes[0]
_fill_operand_expression_list(be2, operand, expression_tree, child_index, expression_list)
return
elif len(children_tree_indexes) == 2:
# infix operator: like "+" in "ebp+10"
child_a = children_tree_indexes[0]
child_b = children_tree_indexes[1]
_fill_operand_expression_list(be2, operand, expression_tree, child_a, expression_list)
expression_list.append(expression)
_fill_operand_expression_list(be2, operand, expression_tree, child_b, expression_list)
return
elif len(children_tree_indexes) == 3:
# infix operator: like "+" in "ebp+ecx+10"
child_a = children_tree_indexes[0]
child_b = children_tree_indexes[1]
child_c = children_tree_indexes[2]
_fill_operand_expression_list(be2, operand, expression_tree, child_a, expression_list)
expression_list.append(expression)
_fill_operand_expression_list(be2, operand, expression_tree, child_b, expression_list)
expression_list.append(expression)
_fill_operand_expression_list(be2, operand, expression_tree, child_c, expression_list)
return
else:
raise NotImplementedError(len(children_tree_indexes))
elif expression.type == BinExport2.Expression.DEREFERENCE:
assert len(children_tree_indexes) == 1
expression_list.append(expression)
child_index = children_tree_indexes[0]
_fill_operand_expression_list(be2, operand, expression_tree, child_index, expression_list)
return
elif expression.type == BinExport2.Expression.IMMEDIATE_FLOAT:
raise NotImplementedError(expression.type)
else:
raise NotImplementedError(expression.type)
def get_operand_expressions(be2: BinExport2, op: BinExport2.Operand) -> list[BinExport2.Expression]:
tree = _build_expression_tree(be2, op)
expressions: list[BinExport2.Expression] = []
_fill_operand_expression_list(be2, op, tree, 0, expressions)
return expressions
def get_operand_register_expression(be2: BinExport2, operand: BinExport2.Operand) -> Optional[BinExport2.Expression]:
if len(operand.expression_index) == 1:
expression: BinExport2.Expression = be2.expression[operand.expression_index[0]]
if expression.type == BinExport2.Expression.REGISTER:
return expression
return None
def get_operand_immediate_expression(be2: BinExport2, operand: BinExport2.Operand) -> Optional[BinExport2.Expression]:
if len(operand.expression_index) == 1:
# - type: IMMEDIATE_INT
# immediate: 20588728364
# parent_index: 0
expression: BinExport2.Expression = be2.expression[operand.expression_index[0]]
if expression.type == BinExport2.Expression.IMMEDIATE_INT:
return expression
elif len(operand.expression_index) == 2:
# from IDA, which provides a size hint for every operand,
# we get the following pattern for immediate constants:
#
# - type: SIZE_PREFIX
# symbol: "b8"
# - type: IMMEDIATE_INT
# immediate: 20588728364
# parent_index: 0
expression0: BinExport2.Expression = be2.expression[operand.expression_index[0]]
expression1: BinExport2.Expression = be2.expression[operand.expression_index[1]]
if expression0.type == BinExport2.Expression.SIZE_PREFIX:
if expression1.type == BinExport2.Expression.IMMEDIATE_INT:
return expression1
return None
def get_instruction_mnemonic(be2: BinExport2, instruction: BinExport2.Instruction) -> str:
return be2.mnemonic[instruction.mnemonic_index].name.lower()
def get_instruction_operands(be2: BinExport2, instruction: BinExport2.Instruction) -> list[BinExport2.Operand]:
return [be2.operand[operand_index] for operand_index in instruction.operand_index]
def split_with_delimiters(s: str, delimiters: tuple[str, ...]) -> Iterator[str]:
"""
Splits a string by any of the provided delimiter characters,
including the delimiters in the results.
Args:
string: The string to split.
delimiters: A string containing the characters to use as delimiters.
"""
start = 0
for i, char in enumerate(s):
if char in delimiters:
yield s[start:i]
yield char
start = i + 1
if start < len(s):
yield s[start:]
BinExport2OperandPattern = Union[str, tuple[str, ...]]
@dataclass
class BinExport2InstructionPattern:
"""
This describes a way to match disassembled instructions, with mnemonics and operands.
You can specify constraints on the instruction, via:
- the mnemonics, like "mov",
- number of operands, and
- format of each operand, "[reg, reg, #int]".
During matching, you can also capture a single element, to see its concrete value.
For example, given the pattern:
mov reg0, #int0 ; capture int0
and the instruction:
mov eax, 1
Then the capture will contain the immediate integer 1.
This matcher uses the BinExport2 data layout under the hood.
"""
mnemonics: tuple[str, ...]
operands: tuple[Union[str, BinExport2OperandPattern], ...]
capture: Optional[str]
@classmethod
def from_str(cls, query: str):
"""
Parse a pattern string into a Pattern instance.
The supported syntax is like this:
br reg
br reg ; capture reg
br reg(stack) ; capture reg
br reg(not-stack) ; capture reg
mov reg0, reg1 ; capture reg0
adrp reg, #int ; capture #int
add reg, reg, #int ; capture #int
ldr reg0, [reg1] ; capture reg1
ldr|str reg, [reg, #int] ; capture #int
ldr|str reg, [reg(stack), #int] ; capture #int
ldr|str reg, [reg(not-stack), #int] ; capture #int
ldr|str reg, [reg, #int]! ; capture #int
ldr|str reg, [reg], #int ; capture #int
ldp|stp reg, reg, [reg, #int] ; capture #int
ldp|stp reg, reg, [reg, #int]! ; capture #int
ldp|stp reg, reg, [reg], #int ; capture #int
"""
#
# The implementation of the parser here is obviously ugly.
# Its handwritten and probably fragile. But since we don't
# expect this to be widely used, its probably ok.
# Don't hesitate to rewrite this if it becomes more important.
#
# Note that this doesn't have to be very performant.
# We expect these patterns to be parsed once upfront and then reused
# (globally at the module level?) rather than within any loop.
#
pattern, _, comment = query.strip().partition(";")
# we don't support fs: yet
assert ":" not in pattern
# from "capture #int" to "#int"
if comment:
comment = comment.strip()
assert comment.startswith("capture ")
capture = comment[len("capture ") :]
else:
capture = None
# from "ldr|str ..." to ["ldr", "str"]
pattern = pattern.strip()
mnemonic, _, rest = pattern.partition(" ")
mnemonics = mnemonic.split("|")
operands: list[Union[str, tuple[str, ...]]] = []
while rest:
rest = rest.strip()
if not rest.startswith("["):
# If its not a dereference, which looks like `[op, op, op, ...]`,
# then its a simple operand, which we can split by the next comma.
operand, _, rest = rest.partition(", ")
rest = rest.strip()
operands.append(operand)
else:
# This looks like a dereference, something like `[op, op, op, ...]`.
# Since these can't be nested, look for the next ] and then parse backwards.
deref_end = rest.index("]")
try:
deref_end = rest.index(", ", deref_end)
deref_end += len(", ")
except ValueError:
deref = rest
rest = ""
else:
deref = rest[:deref_end]
rest = rest[deref_end:]
rest = rest.strip()
deref = deref.rstrip(" ")
deref = deref.rstrip(",")
# like: [reg, #int]!
has_postindex_writeback = deref.endswith("!")
deref = deref.rstrip("!")
deref = deref.rstrip("]")
deref = deref.lstrip("[")
parts = tuple(split_with_delimiters(deref, (",", "+", "*")))
parts = tuple(s.strip() for s in parts)
# emit operands in this order to match
# how BinExport2 expressions are flatted
# by get_operand_expressions
if has_postindex_writeback:
operands.append(("!", "[") + parts)
else:
operands.append(("[",) + parts)
for operand in operands: # type: ignore
# Try to ensure we've parsed the operands correctly.
# This is just sanity checking.
for o in (operand,) if isinstance(operand, str) else operand:
# operands can look like:
# - reg
# - reg0
# - reg(stack)
# - reg0(stack)
# - reg(not-stack)
# - reg0(not-stack)
# - #int
# - #int0
# and a limited set of supported operators.
# use an inline regex so that its easy to read. not perf critical.
assert re.match(r"^(reg|#int)[0-9]?(\(stack\)|\(not-stack\))?$", o) or o in ("[", ",", "!", "+", "*")
return cls(tuple(mnemonics), tuple(operands), capture)
@dataclass
class MatchResult:
operand_index: int
expression_index: int
expression: BinExport2.Expression
def match(
self, mnemonic: str, operand_expressions: list[list[BinExport2.Expression]]
) -> Optional["BinExport2InstructionPattern.MatchResult"]:
"""
Match the given BinExport2 data against this pattern.
The BinExport2 expression tree must have been flattened, such as with
capa.features.extractors.binexport2.helpers.get_operand_expressions.
If there's a match, the captured Expression instance is returned.
Otherwise, you get None back.
"""
if mnemonic not in self.mnemonics:
return None
if len(self.operands) != len(operand_expressions):
return None
captured = None
for operand_index, found_expressions in enumerate(operand_expressions):
wanted_expressions = self.operands[operand_index]
# from `"reg"` to `("reg", )`
if isinstance(wanted_expressions, str):
wanted_expressions = (wanted_expressions,)
assert isinstance(wanted_expressions, tuple)
if len(wanted_expressions) != len(found_expressions):
return None
for expression_index, (wanted_expression, found_expression) in enumerate(
zip(wanted_expressions, found_expressions)
):
if wanted_expression.startswith("reg"):
if found_expression.type != BinExport2.Expression.REGISTER:
return None
if wanted_expression.endswith(")"):
if wanted_expression.endswith("(not-stack)"):
# intel 64: rsp, esp, sp,
# intel 32: ebp, ebp, bp
# arm: sp
register_name = found_expression.symbol.lower()
if register_name in ("rsp", "esp", "sp", "rbp", "ebp", "bp"):
return None
elif wanted_expression.endswith("(stack)"):
register_name = found_expression.symbol.lower()
if register_name not in ("rsp", "esp", "sp", "rbp", "ebp", "bp"):
return None
else:
raise ValueError("unexpected expression suffix", wanted_expression)
if self.capture == wanted_expression:
captured = BinExport2InstructionPattern.MatchResult(
operand_index, expression_index, found_expression
)
elif wanted_expression.startswith("#int"):
if found_expression.type != BinExport2.Expression.IMMEDIATE_INT:
return None
if self.capture == wanted_expression:
captured = BinExport2InstructionPattern.MatchResult(
operand_index, expression_index, found_expression
)
elif wanted_expression == "[":
if found_expression.type != BinExport2.Expression.DEREFERENCE:
return None
elif wanted_expression in (",", "!", "+", "*"):
if found_expression.type != BinExport2.Expression.OPERATOR:
return None
if found_expression.symbol != wanted_expression:
return None
else:
raise ValueError(found_expression)
if captured:
return captured
else:
# There were no captures, so
# return arbitrary non-None expression
return BinExport2InstructionPattern.MatchResult(operand_index, expression_index, found_expression)
class BinExport2InstructionPatternMatcher:
"""Index and match a collection of instruction patterns."""
def __init__(self, queries: list[BinExport2InstructionPattern]):
self.queries = queries
# shard the patterns by (mnemonic, #operands)
self._index: dict[tuple[str, int], list[BinExport2InstructionPattern]] = defaultdict(list)
for query in queries:
for mnemonic in query.mnemonics:
self._index[(mnemonic.lower(), len(query.operands))].append(query)
@classmethod
def from_str(cls, patterns: str):
return cls(
[
BinExport2InstructionPattern.from_str(line)
for line in filter(
lambda line: not line.startswith("#"), (line.strip() for line in patterns.split("\n"))
)
]
)
def match(
self, mnemonic: str, operand_expressions: list[list[BinExport2.Expression]]
) -> Optional[BinExport2InstructionPattern.MatchResult]:
queries = self._index.get((mnemonic.lower(), len(operand_expressions)), [])
for query in queries:
captured = query.match(mnemonic.lower(), operand_expressions)
if captured:
return captured
return None
def match_with_be2(
self, be2: BinExport2, instruction_index: int
) -> Optional[BinExport2InstructionPattern.MatchResult]:
instruction: BinExport2.Instruction = be2.instruction[instruction_index]
mnemonic: str = get_instruction_mnemonic(be2, instruction)
if (mnemonic.lower(), len(instruction.operand_index)) not in self._index:
# verify that we might have a hit before we realize the operand expression list
return None
operands = []
for operand_index in instruction.operand_index:
operands.append(get_operand_expressions(be2, be2.operand[operand_index]))
return self.match(mnemonic, operands)

View File

@@ -0,0 +1,254 @@
# Copyright (C) 2023 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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.
import logging
from typing import Iterator
import capa.features.extractors.helpers
import capa.features.extractors.strings
import capa.features.extractors.binexport2.helpers
import capa.features.extractors.binexport2.arch.arm.insn
import capa.features.extractors.binexport2.arch.intel.insn
from capa.features.insn import API, Mnemonic
from capa.features.common import Bytes, String, Feature, Characteristic
from capa.features.address import Address, AbsoluteVirtualAddress
from capa.features.extractors.binexport2 import (
AddressSpace,
AnalysisContext,
BinExport2Index,
FunctionContext,
ReadMemoryError,
BinExport2Analysis,
InstructionContext,
)
from capa.features.extractors.base_extractor import BBHandle, InsnHandle, FunctionHandle
from capa.features.extractors.binexport2.helpers import HAS_ARCH_ARM, HAS_ARCH_INTEL
from capa.features.extractors.binexport2.binexport2_pb2 import BinExport2
logger = logging.getLogger(__name__)
def extract_insn_api_features(fh: FunctionHandle, _bbh: BBHandle, ih: InsnHandle) -> Iterator[tuple[Feature, Address]]:
fhi: FunctionContext = fh.inner
ii: InstructionContext = ih.inner
be2: BinExport2 = fhi.ctx.be2
be2_index: BinExport2Index = fhi.ctx.idx
be2_analysis: BinExport2Analysis = fhi.ctx.analysis
insn: BinExport2.Instruction = be2.instruction[ii.instruction_index]
for addr in insn.call_target:
addr = be2_analysis.thunks.get(addr, addr)
if addr not in be2_index.vertex_index_by_address:
# disassembler did not define function at address
logger.debug("0x%x is not a vertex", addr)
continue
vertex_idx: int = be2_index.vertex_index_by_address[addr]
vertex: BinExport2.CallGraph.Vertex = be2.call_graph.vertex[vertex_idx]
if not capa.features.extractors.binexport2.helpers.is_vertex_type(
vertex, BinExport2.CallGraph.Vertex.Type.IMPORTED
):
continue
if not vertex.HasField("mangled_name"):
logger.debug("vertex %d does not have mangled_name", vertex_idx)
continue
api_name: str = vertex.mangled_name
for name in capa.features.extractors.helpers.generate_symbols("", api_name):
yield API(name), ih.address
def extract_insn_number_features(
fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle
) -> Iterator[tuple[Feature, Address]]:
fhi: FunctionContext = fh.inner
if fhi.arch & HAS_ARCH_INTEL:
yield from capa.features.extractors.binexport2.arch.intel.insn.extract_insn_number_features(fh, bbh, ih)
elif fhi.arch & HAS_ARCH_ARM:
yield from capa.features.extractors.binexport2.arch.arm.insn.extract_insn_number_features(fh, bbh, ih)
def extract_insn_bytes_features(fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle) -> Iterator[tuple[Feature, Address]]:
fhi: FunctionContext = fh.inner
ii: InstructionContext = ih.inner
ctx: AnalysisContext = fhi.ctx
be2: BinExport2 = ctx.be2
idx: BinExport2Index = ctx.idx
address_space: AddressSpace = ctx.address_space
instruction_index: int = ii.instruction_index
if instruction_index in idx.string_reference_index_by_source_instruction_index:
# disassembler already identified string reference from instruction
return
reference_addresses: list[int] = []
if instruction_index in idx.data_reference_index_by_source_instruction_index:
for data_reference_index in idx.data_reference_index_by_source_instruction_index[instruction_index]:
data_reference: BinExport2.DataReference = be2.data_reference[data_reference_index]
data_reference_address: int = data_reference.address
if data_reference_address in idx.insn_address_by_index:
# appears to be code
continue
reference_addresses.append(data_reference_address)
for reference_address in reference_addresses:
try:
# if at end of segment then there might be an overrun here.
buf: bytes = address_space.read_memory(reference_address, 0x100)
except ReadMemoryError:
logger.debug("failed to read memory: 0x%x", reference_address)
continue
if capa.features.extractors.helpers.all_zeros(buf):
continue
is_string: bool = False
# note: we *always* break after the first iteration
for s in capa.features.extractors.strings.extract_ascii_strings(buf):
if s.offset != 0:
break
yield String(s.s), ih.address
is_string = True
break
# note: we *always* break after the first iteration
for s in capa.features.extractors.strings.extract_unicode_strings(buf):
if s.offset != 0:
break
yield String(s.s), ih.address
is_string = True
break
if not is_string:
yield Bytes(buf), ih.address
def extract_insn_string_features(
fh: FunctionHandle, _bbh: BBHandle, ih: InsnHandle
) -> Iterator[tuple[Feature, Address]]:
fhi: FunctionContext = fh.inner
ii: InstructionContext = ih.inner
be2: BinExport2 = fhi.ctx.be2
idx: BinExport2Index = fhi.ctx.idx
instruction_index: int = ii.instruction_index
if instruction_index in idx.string_reference_index_by_source_instruction_index:
for string_reference_index in idx.string_reference_index_by_source_instruction_index[instruction_index]:
string_reference: BinExport2.Reference = be2.string_reference[string_reference_index]
string_index: int = string_reference.string_table_index
string: str = be2.string_table[string_index]
yield String(string), ih.address
def extract_insn_offset_features(
fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle
) -> Iterator[tuple[Feature, Address]]:
fhi: FunctionContext = fh.inner
if fhi.arch & HAS_ARCH_INTEL:
yield from capa.features.extractors.binexport2.arch.intel.insn.extract_insn_offset_features(fh, bbh, ih)
elif fhi.arch & HAS_ARCH_ARM:
yield from capa.features.extractors.binexport2.arch.arm.insn.extract_insn_offset_features(fh, bbh, ih)
def extract_insn_nzxor_characteristic_features(
fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle
) -> Iterator[tuple[Feature, Address]]:
fhi: FunctionContext = fh.inner
if fhi.arch & HAS_ARCH_INTEL:
yield from capa.features.extractors.binexport2.arch.intel.insn.extract_insn_nzxor_characteristic_features(
fh, bbh, ih
)
elif fhi.arch & HAS_ARCH_ARM:
yield from capa.features.extractors.binexport2.arch.arm.insn.extract_insn_nzxor_characteristic_features(
fh, bbh, ih
)
def extract_insn_mnemonic_features(
fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle
) -> Iterator[tuple[Feature, Address]]:
fhi: FunctionContext = fh.inner
ii: InstructionContext = ih.inner
be2: BinExport2 = fhi.ctx.be2
instruction: BinExport2.Instruction = be2.instruction[ii.instruction_index]
mnemonic: BinExport2.Mnemonic = be2.mnemonic[instruction.mnemonic_index]
mnemonic_name: str = mnemonic.name.lower()
yield Mnemonic(mnemonic_name), ih.address
def extract_function_calls_from(fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle) -> Iterator[tuple[Feature, Address]]:
"""extract functions calls from features
most relevant at the function scope;
however, its most efficient to extract at the instruction scope.
"""
fhi: FunctionContext = fh.inner
ii: InstructionContext = ih.inner
be2: BinExport2 = fhi.ctx.be2
instruction: BinExport2.Instruction = be2.instruction[ii.instruction_index]
for call_target_address in instruction.call_target:
addr: AbsoluteVirtualAddress = AbsoluteVirtualAddress(call_target_address)
yield Characteristic("calls from"), addr
if fh.address == addr:
yield Characteristic("recursive call"), addr
def extract_function_indirect_call_characteristic_features(
fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle
) -> Iterator[tuple[Feature, Address]]:
fhi: FunctionContext = fh.inner
if fhi.arch & HAS_ARCH_INTEL:
yield from capa.features.extractors.binexport2.arch.intel.insn.extract_function_indirect_call_characteristic_features(
fh, bbh, ih
)
elif fhi.arch & HAS_ARCH_ARM:
yield from capa.features.extractors.binexport2.arch.arm.insn.extract_function_indirect_call_characteristic_features(
fh, bbh, ih
)
def extract_features(f: FunctionHandle, bbh: BBHandle, insn: InsnHandle) -> Iterator[tuple[Feature, Address]]:
"""extract instruction features"""
for inst_handler in INSTRUCTION_HANDLERS:
for feature, ea in inst_handler(f, bbh, insn):
yield feature, ea
INSTRUCTION_HANDLERS = (
extract_insn_api_features,
extract_insn_number_features,
extract_insn_bytes_features,
extract_insn_string_features,
extract_insn_offset_features,
extract_insn_nzxor_characteristic_features,
extract_insn_mnemonic_features,
extract_function_calls_from,
extract_function_indirect_call_characteristic_features,
)

View File

@@ -5,171 +5,25 @@
# 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.
from typing import Iterator
import string
import struct
from typing import Tuple, Iterator
from binaryninja import Function, Settings
from binaryninja import BasicBlock as BinjaBasicBlock
from binaryninja import (
BinaryView,
SymbolType,
RegisterValueType,
VariableSourceType,
MediumLevelILSetVar,
MediumLevelILOperation,
MediumLevelILBasicBlock,
MediumLevelILInstruction,
)
from capa.features.common import Feature, Characteristic
from capa.features.address import Address
from capa.features.basicblock import BasicBlock
from capa.features.extractors.helpers import MIN_STACKSTRING_LEN
from capa.features.extractors.base_extractor import BBHandle, FunctionHandle
use_const_outline: bool = False
settings: Settings = Settings()
if settings.contains("analysis.outlining.builtins") and settings.get_bool("analysis.outlining.builtins"):
use_const_outline = True
def get_printable_len_ascii(s: bytes) -> int:
"""Return string length if all operand bytes are ascii or utf16-le printable"""
count = 0
for c in s:
if c == 0:
return count
if c < 127 and chr(c) in string.printable:
count += 1
return count
def get_printable_len_wide(s: bytes) -> int:
"""Return string length if all operand bytes are ascii or utf16-le printable"""
if all(c == 0x00 for c in s[1::2]):
return get_printable_len_ascii(s[::2])
return 0
def get_stack_string_len(f: Function, il: MediumLevelILInstruction) -> int:
bv: BinaryView = f.view
if il.operation != MediumLevelILOperation.MLIL_CALL:
return 0
target = il.dest
if target.operation not in [MediumLevelILOperation.MLIL_CONST, MediumLevelILOperation.MLIL_CONST_PTR]:
return 0
addr = target.value.value
sym = bv.get_symbol_at(addr)
if not sym or sym.type != SymbolType.LibraryFunctionSymbol:
return 0
if sym.name not in ["__builtin_strncpy", "__builtin_strcpy", "__builtin_wcscpy"]:
return 0
if len(il.params) < 2:
return 0
dest = il.params[0]
if dest.operation != MediumLevelILOperation.MLIL_ADDRESS_OF:
return 0
var = dest.src
if var.source_type != VariableSourceType.StackVariableSourceType:
return 0
src = il.params[1]
if src.value.type != RegisterValueType.ConstantDataAggregateValue:
return 0
s = f.get_constant_data(RegisterValueType.ConstantDataAggregateValue, src.value.value)
return max(get_printable_len_ascii(bytes(s)), get_printable_len_wide(bytes(s)))
def get_printable_len(il: MediumLevelILSetVar) -> int:
"""Return string length if all operand bytes are ascii or utf16-le printable"""
width = il.dest.type.width
value = il.src.value.value
if width == 1:
chars = struct.pack("<B", value & 0xFF)
elif width == 2:
chars = struct.pack("<H", value & 0xFFFF)
elif width == 4:
chars = struct.pack("<I", value & 0xFFFFFFFF)
elif width == 8:
chars = struct.pack("<Q", value & 0xFFFFFFFFFFFFFFFF)
else:
return 0
def is_printable_ascii(chars_: bytes):
return all(c < 127 and chr(c) in string.printable for c in chars_)
def is_printable_utf16le(chars_: bytes):
if all(c == 0x00 for c in chars_[1::2]):
return is_printable_ascii(chars_[::2])
if is_printable_ascii(chars):
return width
if is_printable_utf16le(chars):
return width // 2
return 0
def is_mov_imm_to_stack(il: MediumLevelILInstruction) -> bool:
"""verify instruction moves immediate onto stack"""
if il.operation != MediumLevelILOperation.MLIL_SET_VAR:
return False
if il.src.operation != MediumLevelILOperation.MLIL_CONST:
return False
if il.dest.source_type != VariableSourceType.StackVariableSourceType:
return False
return True
def bb_contains_stackstring(f: Function, bb: MediumLevelILBasicBlock) -> bool:
"""check basic block for stackstring indicators
true if basic block contains enough moves of constant bytes to the stack
"""
count = 0
for il in bb:
if use_const_outline:
count += get_stack_string_len(f, il)
else:
if is_mov_imm_to_stack(il):
count += get_printable_len(il)
if count > MIN_STACKSTRING_LEN:
return True
return False
def extract_bb_stackstring(fh: FunctionHandle, bbh: BBHandle) -> Iterator[Tuple[Feature, Address]]:
"""extract stackstring indicators from basic block"""
bb: Tuple[BinjaBasicBlock, MediumLevelILBasicBlock] = bbh.inner
if bb[1] is not None and bb_contains_stackstring(fh.inner, bb[1]):
yield Characteristic("stack string"), bbh.address
def extract_bb_tight_loop(fh: FunctionHandle, bbh: BBHandle) -> Iterator[Tuple[Feature, Address]]:
def extract_bb_tight_loop(fh: FunctionHandle, bbh: BBHandle) -> Iterator[tuple[Feature, Address]]:
"""extract tight loop indicators from a basic block"""
bb: Tuple[BinjaBasicBlock, MediumLevelILBasicBlock] = bbh.inner
for edge in bb[0].outgoing_edges:
if edge.target.start == bb[0].start:
bb: BinjaBasicBlock = bbh.inner
for edge in bb.outgoing_edges:
if edge.target.start == bb.start:
yield Characteristic("tight loop"), bbh.address
def extract_features(fh: FunctionHandle, bbh: BBHandle) -> Iterator[Tuple[Feature, Address]]:
def extract_features(fh: FunctionHandle, bbh: BBHandle) -> Iterator[tuple[Feature, Address]]:
"""extract basic block features"""
for bb_handler in BASIC_BLOCK_HANDLERS:
for feature, addr in bb_handler(fh, bbh):
@@ -177,7 +31,4 @@ def extract_features(fh: FunctionHandle, bbh: BBHandle) -> Iterator[Tuple[Featur
yield BasicBlock(), bbh.address
BASIC_BLOCK_HANDLERS = (
extract_bb_tight_loop,
extract_bb_stackstring,
)
BASIC_BLOCK_HANDLERS = (extract_bb_tight_loop,)

View File

@@ -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.
from typing import List, Tuple, Iterator
from typing import Iterator
import binaryninja as binja
@@ -17,14 +17,20 @@ import capa.features.extractors.binja.function
import capa.features.extractors.binja.basicblock
from capa.features.common import Feature
from capa.features.address import Address, AbsoluteVirtualAddress
from capa.features.extractors.base_extractor import BBHandle, InsnHandle, FunctionHandle, FeatureExtractor
from capa.features.extractors.base_extractor import (
BBHandle,
InsnHandle,
SampleHashes,
FunctionHandle,
StaticFeatureExtractor,
)
class BinjaFeatureExtractor(FeatureExtractor):
class BinjaFeatureExtractor(StaticFeatureExtractor):
def __init__(self, bv: binja.BinaryView):
super().__init__()
super().__init__(hashes=SampleHashes.from_bytes(bv.file.raw.read(0, bv.file.raw.length)))
self.bv = bv
self.global_features: List[Tuple[Feature, Address]] = []
self.global_features: list[tuple[Feature, Address]] = []
self.global_features.extend(capa.features.extractors.binja.file.extract_file_format(self.bv))
self.global_features.extend(capa.features.extractors.binja.global_.extract_os(self.bv))
self.global_features.extend(capa.features.extractors.binja.global_.extract_arch(self.bv))
@@ -42,31 +48,24 @@ class BinjaFeatureExtractor(FeatureExtractor):
for f in self.bv.functions:
yield FunctionHandle(address=AbsoluteVirtualAddress(f.start), inner=f)
def extract_function_features(self, fh: FunctionHandle) -> Iterator[Tuple[Feature, Address]]:
def extract_function_features(self, fh: FunctionHandle) -> Iterator[tuple[Feature, Address]]:
yield from capa.features.extractors.binja.function.extract_features(fh)
def get_basic_blocks(self, fh: FunctionHandle) -> Iterator[BBHandle]:
f: binja.Function = fh.inner
# Set up a MLIL basic block dict look up to associate the disassembly basic block with its MLIL basic block
mlil_lookup = {}
for mlil_bb in f.mlil.basic_blocks:
mlil_lookup[mlil_bb.source_block.start] = mlil_bb
for bb in f.basic_blocks:
mlil_bb = mlil_lookup.get(bb.start)
yield BBHandle(address=AbsoluteVirtualAddress(bb.start), inner=bb)
yield BBHandle(address=AbsoluteVirtualAddress(bb.start), inner=(bb, mlil_bb))
def extract_basic_block_features(self, fh: FunctionHandle, bbh: BBHandle) -> Iterator[Tuple[Feature, Address]]:
def extract_basic_block_features(self, fh: FunctionHandle, bbh: BBHandle) -> Iterator[tuple[Feature, Address]]:
yield from capa.features.extractors.binja.basicblock.extract_features(fh, bbh)
def get_instructions(self, fh: FunctionHandle, bbh: BBHandle) -> Iterator[InsnHandle]:
import capa.features.extractors.binja.helpers as binja_helpers
bb: Tuple[binja.BasicBlock, binja.MediumLevelILBasicBlock] = bbh.inner
addr = bb[0].start
bb: binja.BasicBlock = bbh.inner
addr = bb.start
for text, length in bb[0]:
for text, length in bb:
insn = binja_helpers.DisassemblyInstruction(addr, length, text)
yield InsnHandle(address=AbsoluteVirtualAddress(addr), inner=insn)
addr += length

View File

@@ -5,9 +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.
import struct
from typing import Tuple, Iterator
from typing import Iterator
from binaryninja import Segment, BinaryView, SymbolType, SymbolBinding
@@ -15,75 +13,76 @@ import capa.features.extractors.common
import capa.features.extractors.helpers
import capa.features.extractors.strings
from capa.features.file import Export, Import, Section, FunctionName
from capa.features.common import FORMAT_PE, FORMAT_ELF, Format, String, Feature, Characteristic
from capa.features.common import (
FORMAT_PE,
FORMAT_ELF,
FORMAT_SC32,
FORMAT_SC64,
FORMAT_BINJA_DB,
Format,
String,
Feature,
Characteristic,
)
from capa.features.address import NO_ADDRESS, Address, FileOffsetAddress, AbsoluteVirtualAddress
from capa.features.extractors.binja.helpers import unmangle_c_name
from capa.features.extractors.binja.helpers import read_c_string, unmangle_c_name
def check_segment_for_pe(bv: BinaryView, seg: Segment) -> Iterator[Tuple[int, int]]:
"""check segment for embedded PE
adapted for binja from:
https://github.com/vivisect/vivisect/blob/7be4037b1cecc4551b397f840405a1fc606f9b53/PE/carve.py#L19
"""
mz_xor = [
(
capa.features.extractors.helpers.xor_static(b"MZ", i),
capa.features.extractors.helpers.xor_static(b"PE", i),
i,
)
for i in range(256)
]
todo = []
# If this is the first segment of the binary, skip the first bytes. Otherwise, there will always be a matched
# PE at the start of the binaryview.
start = seg.start
if bv.view_type == "PE" and start == bv.start:
def check_segment_for_pe(bv: BinaryView, seg: Segment) -> Iterator[tuple[Feature, Address]]:
"""check segment for embedded PE"""
start = 0
if bv.view_type == "PE" and seg.start == bv.start:
# If this is the first segment of the binary, skip the first bytes.
# Otherwise, there will always be a matched PE at the start of the binaryview.
start += 1
for mzx, pex, i in mz_xor:
for off, _ in bv.find_all_data(start, seg.end, mzx):
todo.append((off, mzx, pex, i))
buf = bv.read(seg.start, seg.length)
while len(todo):
off, mzx, pex, i = todo.pop()
# The MZ header has one field we will check e_lfanew is at 0x3c
e_lfanew = off + 0x3C
if seg.end < (e_lfanew + 4):
continue
newoff = struct.unpack("<I", capa.features.extractors.helpers.xor_static(bv.read(e_lfanew, 4), i))[0]
peoff = off + newoff
if seg.end < (peoff + 2):
continue
if bv.read(peoff, 2) == pex:
yield off, i
for offset, _ in capa.features.extractors.helpers.carve_pe(buf, start):
yield Characteristic("embedded pe"), FileOffsetAddress(seg.start + offset)
def extract_file_embedded_pe(bv: BinaryView) -> Iterator[Tuple[Feature, Address]]:
def extract_file_embedded_pe(bv: BinaryView) -> Iterator[tuple[Feature, Address]]:
"""extract embedded PE features"""
for seg in bv.segments:
for ea, _ in check_segment_for_pe(bv, seg):
yield Characteristic("embedded pe"), FileOffsetAddress(ea)
yield from check_segment_for_pe(bv, seg)
def extract_file_export_names(bv: BinaryView) -> Iterator[Tuple[Feature, Address]]:
def extract_file_export_names(bv: BinaryView) -> Iterator[tuple[Feature, Address]]:
"""extract function exports"""
for sym in bv.get_symbols_of_type(SymbolType.FunctionSymbol):
for sym in bv.get_symbols_of_type(SymbolType.FunctionSymbol) + bv.get_symbols_of_type(SymbolType.DataSymbol):
if sym.binding in [SymbolBinding.GlobalBinding, SymbolBinding.WeakBinding]:
name = sym.short_name
yield Export(name), AbsoluteVirtualAddress(sym.address)
unmangled_name = unmangle_c_name(name)
if name != unmangled_name:
yield Export(unmangled_name), AbsoluteVirtualAddress(sym.address)
if name.startswith("__forwarder_name(") and name.endswith(")"):
yield Export(name[17:-1]), AbsoluteVirtualAddress(sym.address)
yield Characteristic("forwarded export"), AbsoluteVirtualAddress(sym.address)
else:
yield Export(name), AbsoluteVirtualAddress(sym.address)
unmangled_name = unmangle_c_name(name)
if name != unmangled_name:
yield Export(unmangled_name), AbsoluteVirtualAddress(sym.address)
for sym in bv.get_symbols_of_type(SymbolType.DataSymbol):
if sym.binding not in [SymbolBinding.GlobalBinding]:
continue
name = sym.short_name
if not name.startswith("__forwarder_name"):
continue
# Due to https://github.com/Vector35/binaryninja-api/issues/4641, in binja version 3.5, the symbol's name
# does not contain the DLL name. As a workaround, we read the C string at the symbol's address, which contains
# both the DLL name and the function name.
# Once the above issue is closed in the next binjs stable release, we can update the code here to use the
# symbol name directly.
name = read_c_string(bv, sym.address, 1024)
forwarded_name = capa.features.extractors.helpers.reformat_forwarded_export_name(name)
yield Export(forwarded_name), AbsoluteVirtualAddress(sym.address)
yield Characteristic("forwarded export"), AbsoluteVirtualAddress(sym.address)
def extract_file_import_names(bv: BinaryView) -> Iterator[Tuple[Feature, Address]]:
def extract_file_import_names(bv: BinaryView) -> Iterator[tuple[Feature, Address]]:
"""extract function imports
1. imports by ordinal:
@@ -97,51 +96,63 @@ def extract_file_import_names(bv: BinaryView) -> Iterator[Tuple[Feature, Address
for sym in bv.get_symbols_of_type(SymbolType.ImportAddressSymbol):
lib_name = str(sym.namespace)
addr = AbsoluteVirtualAddress(sym.address)
for name in capa.features.extractors.helpers.generate_symbols(lib_name, sym.short_name):
for name in capa.features.extractors.helpers.generate_symbols(lib_name, sym.short_name, include_dll=True):
yield Import(name), addr
ordinal = sym.ordinal
if ordinal != 0 and (lib_name != ""):
ordinal_name = f"#{ordinal}"
for name in capa.features.extractors.helpers.generate_symbols(lib_name, ordinal_name):
for name in capa.features.extractors.helpers.generate_symbols(lib_name, ordinal_name, include_dll=True):
yield Import(name), addr
def extract_file_section_names(bv: BinaryView) -> Iterator[Tuple[Feature, Address]]:
def extract_file_section_names(bv: BinaryView) -> Iterator[tuple[Feature, Address]]:
"""extract section names"""
for name, section in bv.sections.items():
yield Section(name), AbsoluteVirtualAddress(section.start)
def extract_file_strings(bv: BinaryView) -> Iterator[Tuple[Feature, Address]]:
def extract_file_strings(bv: BinaryView) -> Iterator[tuple[Feature, Address]]:
"""extract ASCII and UTF-16 LE strings"""
for s in bv.strings:
yield String(s.value), FileOffsetAddress(s.start)
def extract_file_function_names(bv: BinaryView) -> Iterator[Tuple[Feature, Address]]:
def extract_file_function_names(bv: BinaryView) -> Iterator[tuple[Feature, Address]]:
"""
extract the names of statically-linked library functions.
"""
for sym_name in bv.symbols:
for sym in bv.symbols[sym_name]:
if sym.type == SymbolType.LibraryFunctionSymbol:
name = sym.short_name
yield FunctionName(name), sym.address
if name.startswith("_"):
# some linkers may prefix linked routines with a `_` to avoid name collisions.
# extract features for both the mangled and un-mangled representations.
# e.g. `_fwrite` -> `fwrite`
# see: https://stackoverflow.com/a/2628384/87207
yield FunctionName(name[1:]), sym.address
if sym.type not in [SymbolType.LibraryFunctionSymbol, SymbolType.FunctionSymbol]:
continue
name = sym.short_name
yield FunctionName(name), sym.address
if name.startswith("_"):
# some linkers may prefix linked routines with a `_` to avoid name collisions.
# extract features for both the mangled and un-mangled representations.
# e.g. `_fwrite` -> `fwrite`
# see: https://stackoverflow.com/a/2628384/87207
yield FunctionName(name[1:]), sym.address
def extract_file_format(bv: BinaryView) -> Iterator[Tuple[Feature, Address]]:
def extract_file_format(bv: BinaryView) -> Iterator[tuple[Feature, Address]]:
if bv.file.database is not None:
yield Format(FORMAT_BINJA_DB), NO_ADDRESS
view_type = bv.view_type
if view_type in ["PE", "COFF"]:
yield Format(FORMAT_PE), NO_ADDRESS
elif view_type == "ELF":
yield Format(FORMAT_ELF), NO_ADDRESS
elif view_type == "Mapped":
if bv.arch.name == "x86":
yield Format(FORMAT_SC32), NO_ADDRESS
elif bv.arch.name == "x86_64":
yield Format(FORMAT_SC64), NO_ADDRESS
else:
raise NotImplementedError(f"unexpected raw file with arch: {bv.arch}")
elif view_type == "Raw":
# no file type to return when processing a binary file, but we want to continue processing
return
@@ -149,7 +160,7 @@ def extract_file_format(bv: BinaryView) -> Iterator[Tuple[Feature, Address]]:
raise NotImplementedError(f"unexpected file format: {view_type}")
def extract_features(bv: BinaryView) -> Iterator[Tuple[Feature, Address]]:
def extract_features(bv: BinaryView) -> Iterator[tuple[Feature, Address]]:
"""extract file features"""
for file_handler in FILE_HANDLERS:
for feature, addr in file_handler(bv):

View File

@@ -5,31 +5,175 @@
# 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.
import os
import sys
import logging
import subprocess
import importlib.util
from typing import Optional
from pathlib import Path
logger = logging.getLogger(__name__)
# When the script gets executed as a standalone executable (via PyInstaller), `import binaryninja` does not work because
# we have excluded the binaryninja module in `pyinstaller.spec`. The trick here is to call the system Python and try
# to find out the path of the binaryninja module that has been installed.
# Note, including the binaryninja module in the `pyintaller.spec` would not work, since the binaryninja module tries to
# Note, including the binaryninja module in the `pyinstaller.spec` would not work, since the binaryninja module tries to
# find the binaryninja core e.g., `libbinaryninjacore.dylib`, using a relative path. And this does not work when the
# binaryninja module is extracted by the PyInstaller.
code = r"""
CODE = r"""
from pathlib import Path
from importlib import util
spec = util.find_spec('binaryninja')
if spec is not None:
if len(spec.submodule_search_locations) > 0:
path = Path(spec.submodule_search_locations[0])
# encode the path with utf8 then convert to hex, make sure it can be read and restored properly
print(str(path.parent).encode('utf8').hex())
path = Path(spec.submodule_search_locations[0])
# encode the path with utf8 then convert to hex, make sure it can be read and restored properly
print(str(path.parent).encode('utf8').hex())
"""
def find_binja_path() -> Path:
raw_output = subprocess.check_output(["python", "-c", code]).decode("ascii").strip()
return Path(bytes.fromhex(raw_output).decode("utf8"))
def find_binaryninja_path_via_subprocess() -> Optional[Path]:
raw_output = subprocess.check_output(["python", "-c", CODE]).decode("ascii").strip()
output = bytes.fromhex(raw_output).decode("utf8")
if not output.strip():
return None
return Path(output)
def get_desktop_entry(name: str) -> Optional[Path]:
"""
Find the path for the given XDG Desktop Entry name.
Like:
>> get_desktop_entry("com.vector35.binaryninja.desktop")
Path("~/.local/share/applications/com.vector35.binaryninja.desktop")
"""
assert sys.platform in ("linux", "linux2")
assert name.endswith(".desktop")
data_dirs = os.environ.get("XDG_DATA_DIRS", "/usr/share") + f":{Path.home()}/.local/share"
for data_dir in data_dirs.split(":"):
applications = Path(data_dir) / "applications"
for application in applications.glob("*.desktop"):
if application.name == name:
return application
return None
def get_binaryninja_path(desktop_entry: Path) -> Optional[Path]:
# from: Exec=/home/wballenthin/software/binaryninja/binaryninja %u
# to: /home/wballenthin/software/binaryninja/
for line in desktop_entry.read_text(encoding="utf-8").splitlines():
if not line.startswith("Exec="):
continue
if not line.endswith("binaryninja %u"):
continue
binaryninja_path = Path(line[len("Exec=") : -len("binaryninja %u")])
if not binaryninja_path.exists():
return None
return binaryninja_path
return None
def validate_binaryninja_path(binaryninja_path: Path) -> bool:
if not binaryninja_path:
return False
module_path = binaryninja_path / "python"
if not module_path.is_dir():
return False
if not (module_path / "binaryninja" / "__init__.py").is_file():
return False
return True
def find_binaryninja() -> Optional[Path]:
binaryninja_path = find_binaryninja_path_via_subprocess()
if not binaryninja_path or not validate_binaryninja_path(binaryninja_path):
if sys.platform == "linux" or sys.platform == "linux2":
# ok
logger.debug("detected OS: linux")
elif sys.platform == "darwin":
logger.warning("unsupported platform to find Binary Ninja: %s", sys.platform)
return None
elif sys.platform == "win32":
logger.warning("unsupported platform to find Binary Ninja: %s", sys.platform)
return None
else:
logger.warning("unsupported platform to find Binary Ninja: %s", sys.platform)
return None
desktop_entry = get_desktop_entry("com.vector35.binaryninja.desktop")
if not desktop_entry:
logger.debug("failed to find Binary Ninja application")
return None
logger.debug("found Binary Ninja application: %s", desktop_entry)
binaryninja_path = get_binaryninja_path(desktop_entry)
if not binaryninja_path:
logger.debug("failed to determine Binary Ninja installation path")
return None
if not validate_binaryninja_path(binaryninja_path):
logger.debug("failed to validate Binary Ninja installation")
return None
logger.debug("found Binary Ninja installation: %s", binaryninja_path)
return binaryninja_path / "python"
def is_binaryninja_installed() -> bool:
"""Is the binaryninja module ready to import?"""
try:
return importlib.util.find_spec("binaryninja") is not None
except ModuleNotFoundError:
return False
def has_binaryninja() -> bool:
if is_binaryninja_installed():
logger.debug("found installed Binary Ninja API")
return True
logger.debug("Binary Ninja API not installed, searching...")
binaryninja_path = find_binaryninja()
if not binaryninja_path:
logger.debug("failed to find Binary Ninja installation")
logger.debug("found Binary Ninja API: %s", binaryninja_path)
return binaryninja_path is not None
def load_binaryninja() -> bool:
try:
import binaryninja
return True
except ImportError:
binaryninja_path = find_binaryninja()
if not binaryninja_path:
return False
sys.path.append(binaryninja_path.absolute().as_posix())
try:
import binaryninja # noqa: F401 unused import
return True
except ImportError:
return False
if __name__ == "__main__":
print(find_binja_path())
print(find_binaryninja_path_via_subprocess())

View File

@@ -5,13 +5,28 @@
# 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.
from typing import Tuple, Iterator
import string
from typing import Iterator
from binaryninja import Function, BinaryView, LowLevelILOperation
from binaryninja import (
Function,
BinaryView,
SymbolType,
ILException,
RegisterValueType,
VariableSourceType,
LowLevelILOperation,
MediumLevelILOperation,
MediumLevelILBasicBlock,
MediumLevelILInstruction,
)
from capa.features.file import FunctionName
from capa.features.common import Feature, Characteristic
from capa.features.address import Address, AbsoluteVirtualAddress
from capa.features.extractors import loops
from capa.features.extractors.helpers import MIN_STACKSTRING_LEN
from capa.features.extractors.binja.helpers import get_llil_instr_at_addr
from capa.features.extractors.base_extractor import FunctionHandle
@@ -23,13 +38,26 @@ def extract_function_calls_to(fh: FunctionHandle):
# Everything that is a code reference to the current function is considered a caller, which actually includes
# many other references that are NOT a caller. For example, an instruction `push function_start` will also be
# considered a caller to the function
if caller.llil is not None and caller.llil.operation in [
llil = get_llil_instr_at_addr(func.view, caller.address)
if (llil is None) or llil.operation not in [
LowLevelILOperation.LLIL_CALL,
LowLevelILOperation.LLIL_CALL_STACK_ADJUST,
LowLevelILOperation.LLIL_JUMP,
LowLevelILOperation.LLIL_TAILCALL,
]:
yield Characteristic("calls to"), AbsoluteVirtualAddress(caller.address)
continue
if llil.dest.operation not in [
LowLevelILOperation.LLIL_CONST,
LowLevelILOperation.LLIL_CONST_PTR,
]:
continue
address = llil.dest.constant
if address != func.start:
continue
yield Characteristic("calls to"), AbsoluteVirtualAddress(caller.address)
def extract_function_loop(fh: FunctionHandle):
@@ -59,10 +87,124 @@ def extract_recursive_call(fh: FunctionHandle):
yield Characteristic("recursive call"), fh.address
def extract_features(fh: FunctionHandle) -> Iterator[Tuple[Feature, Address]]:
def extract_function_name(fh: FunctionHandle):
"""extract function names (e.g., symtab names)"""
func: Function = fh.inner
bv: BinaryView = func.view
if bv is None:
return
for sym in bv.get_symbols(func.start):
if sym.type not in [SymbolType.LibraryFunctionSymbol, SymbolType.FunctionSymbol]:
continue
name = sym.short_name
yield FunctionName(name), sym.address
if name.startswith("_"):
# some linkers may prefix linked routines with a `_` to avoid name collisions.
# extract features for both the mangled and un-mangled representations.
# e.g. `_fwrite` -> `fwrite`
# see: https://stackoverflow.com/a/2628384/87207
yield FunctionName(name[1:]), sym.address
def get_printable_len_ascii(s: bytes) -> int:
"""Return string length if all operand bytes are ascii or utf16-le printable"""
count = 0
for c in s:
if c == 0:
return count
if c < 127 and chr(c) in string.printable:
count += 1
return count
def get_printable_len_wide(s: bytes) -> int:
"""Return string length if all operand bytes are ascii or utf16-le printable"""
if all(c == 0x00 for c in s[1::2]):
return get_printable_len_ascii(s[::2])
return 0
def get_stack_string_len(f: Function, il: MediumLevelILInstruction) -> int:
bv: BinaryView = f.view
if il.operation != MediumLevelILOperation.MLIL_CALL:
return 0
target = il.dest
if target.operation not in [MediumLevelILOperation.MLIL_CONST, MediumLevelILOperation.MLIL_CONST_PTR]:
return 0
addr = target.value.value
sym = bv.get_symbol_at(addr)
if not sym or sym.type not in [SymbolType.LibraryFunctionSymbol, SymbolType.SymbolicFunctionSymbol]:
return 0
if sym.name not in ["__builtin_strncpy", "__builtin_strcpy", "__builtin_wcscpy"]:
return 0
if len(il.params) < 2:
return 0
dest = il.params[0]
if dest.operation in [MediumLevelILOperation.MLIL_ADDRESS_OF, MediumLevelILOperation.MLIL_VAR]:
var = dest.src
else:
return 0
if var.source_type != VariableSourceType.StackVariableSourceType:
return 0
src = il.params[1]
if src.value.type != RegisterValueType.ConstantDataAggregateValue:
return 0
s = f.get_constant_data(RegisterValueType.ConstantDataAggregateValue, src.value.value)
return max(get_printable_len_ascii(bytes(s)), get_printable_len_wide(bytes(s)))
def bb_contains_stackstring(f: Function, bb: MediumLevelILBasicBlock) -> bool:
"""check basic block for stackstring indicators
true if basic block contains enough moves of constant bytes to the stack
"""
count = 0
for il in bb:
count += get_stack_string_len(f, il)
if count > MIN_STACKSTRING_LEN:
return True
return False
def extract_stackstring(fh: FunctionHandle):
"""extract stackstring indicators"""
func: Function = fh.inner
bv: BinaryView = func.view
if bv is None:
return
try:
mlil = func.mlil
except ILException:
return
for block in mlil.basic_blocks:
if bb_contains_stackstring(func, block):
yield Characteristic("stack string"), block.source_block.start
def extract_features(fh: FunctionHandle) -> Iterator[tuple[Feature, Address]]:
for func_handler in FUNCTION_HANDLERS:
for feature, addr in func_handler(fh):
yield feature, addr
FUNCTION_HANDLERS = (extract_function_calls_to, extract_function_loop, extract_recursive_call)
FUNCTION_HANDLERS = (
extract_function_calls_to,
extract_function_loop,
extract_recursive_call,
extract_function_name,
extract_stackstring,
)

View File

@@ -6,7 +6,7 @@
# 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.
import logging
from typing import Tuple, Iterator
from typing import Iterator
from binaryninja import BinaryView
@@ -16,7 +16,7 @@ from capa.features.address import NO_ADDRESS, Address
logger = logging.getLogger(__name__)
def extract_os(bv: BinaryView) -> Iterator[Tuple[Feature, Address]]:
def extract_os(bv: BinaryView) -> Iterator[tuple[Feature, Address]]:
name = bv.platform.name
if "-" in name:
name = name.split("-")[0]
@@ -45,7 +45,7 @@ def extract_os(bv: BinaryView) -> Iterator[Tuple[Feature, Address]]:
return
def extract_arch(bv: BinaryView) -> Iterator[Tuple[Feature, Address]]:
def extract_arch(bv: BinaryView) -> Iterator[tuple[Feature, Address]]:
arch = bv.arch.name
if arch == "x86_64":
yield Arch(ARCH_AMD64), NO_ADDRESS

View File

@@ -6,10 +6,10 @@
# 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.
import re
from typing import List, Callable
from typing import Callable, Optional
from dataclasses import dataclass
from binaryninja import LowLevelILInstruction
from binaryninja import BinaryView, LowLevelILFunction, LowLevelILInstruction
from binaryninja.architecture import InstructionTextToken
@@ -17,7 +17,7 @@ from binaryninja.architecture import InstructionTextToken
class DisassemblyInstruction:
address: int
length: int
text: List[InstructionTextToken]
text: list[InstructionTextToken]
LLIL_VISITOR = Callable[[LowLevelILInstruction, LowLevelILInstruction, int], bool]
@@ -51,3 +51,29 @@ def unmangle_c_name(name: str) -> str:
return match.group(1)
return name
def read_c_string(bv: BinaryView, offset: int, max_len: int) -> str:
s: list[str] = []
while len(s) < max_len:
try:
c = bv.read(offset + len(s), 1)[0]
except Exception:
break
if c == 0:
break
s.append(chr(c))
return "".join(s)
def get_llil_instr_at_addr(bv: BinaryView, addr: int) -> Optional[LowLevelILInstruction]:
arch = bv.arch
buffer = bv.read(addr, arch.max_instr_length)
llil = LowLevelILFunction(arch=arch)
llil.current_address = addr
if arch.get_instruction_low_level_il(buffer, addr, llil) == 0:
return None
return llil[0]

View File

@@ -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.
from typing import Any, List, Tuple, Iterator, Optional
from typing import Any, Iterator, Optional
from binaryninja import Function
from binaryninja import BasicBlock as BinjaBasicBlock
@@ -23,7 +23,7 @@ import capa.features.extractors.helpers
from capa.features.insn import API, MAX_STRUCTURE_SIZE, Number, Offset, Mnemonic, OperandNumber, OperandOffset
from capa.features.common import MAX_BYTES_FEATURE_SIZE, Bytes, String, Feature, Characteristic
from capa.features.address import Address, AbsoluteVirtualAddress
from capa.features.extractors.binja.helpers import DisassemblyInstruction, visit_llil_exprs
from capa.features.extractors.binja.helpers import DisassemblyInstruction, visit_llil_exprs, get_llil_instr_at_addr
from capa.features.extractors.base_extractor import BBHandle, InsnHandle, FunctionHandle
# security cookie checks may perform non-zeroing XORs, these are expected within a certain
@@ -36,35 +36,27 @@ SECURITY_COOKIE_BYTES_DELTA = 0x40
# 2. The function must only make one call/jump to another address
# If the function being checked is a stub function, returns the target address. Otherwise, return None.
def is_stub_function(bv: BinaryView, addr: int) -> Optional[int]:
funcs = bv.get_functions_at(addr)
for func in funcs:
if len(func.basic_blocks) != 1:
continue
llil = get_llil_instr_at_addr(bv, addr)
if llil is None or llil.operation not in [
LowLevelILOperation.LLIL_CALL,
LowLevelILOperation.LLIL_CALL_STACK_ADJUST,
LowLevelILOperation.LLIL_JUMP,
LowLevelILOperation.LLIL_TAILCALL,
]:
return None
call_count = 0
call_target = None
for il in func.llil.instructions:
if il.operation in [
LowLevelILOperation.LLIL_CALL,
LowLevelILOperation.LLIL_CALL_STACK_ADJUST,
LowLevelILOperation.LLIL_JUMP,
LowLevelILOperation.LLIL_TAILCALL,
]:
call_count += 1
if il.dest.value.type in [
RegisterValueType.ImportedAddressValue,
RegisterValueType.ConstantValue,
RegisterValueType.ConstantPointerValue,
]:
call_target = il.dest.value.value
# The LLIL instruction retrieved by `get_llil_instr_at_addr` did not go through a full analysis, so we cannot check
# `llil.dest.value.type` here
if llil.dest.operation not in [
LowLevelILOperation.LLIL_CONST,
LowLevelILOperation.LLIL_CONST_PTR,
]:
return None
if call_count == 1 and call_target is not None:
return call_target
return None
return llil.dest.constant
def extract_insn_api_features(fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle) -> Iterator[Tuple[Feature, Address]]:
def extract_insn_api_features(fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle) -> Iterator[tuple[Feature, Address]]:
"""
parse instruction API features
@@ -94,32 +86,36 @@ def extract_insn_api_features(fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle)
candidate_addrs.append(stub_addr)
for address in candidate_addrs:
sym = func.view.get_symbol_at(address)
if sym is None or sym.type not in [SymbolType.ImportAddressSymbol, SymbolType.ImportedFunctionSymbol]:
continue
for sym in func.view.get_symbols(address):
if sym is None or sym.type not in [
SymbolType.ImportAddressSymbol,
SymbolType.ImportedFunctionSymbol,
SymbolType.FunctionSymbol,
]:
continue
sym_name = sym.short_name
sym_name = sym.short_name
lib_name = ""
import_lib = bv.lookup_imported_object_library(sym.address)
if import_lib is not None:
lib_name = import_lib[0].name
if lib_name.endswith(".dll"):
lib_name = lib_name[:-4]
elif lib_name.endswith(".so"):
lib_name = lib_name[:-3]
lib_name = ""
import_lib = bv.lookup_imported_object_library(sym.address)
if import_lib is not None:
lib_name = import_lib[0].name
if lib_name.endswith(".dll"):
lib_name = lib_name[:-4]
elif lib_name.endswith(".so"):
lib_name = lib_name[:-3]
for name in capa.features.extractors.helpers.generate_symbols(lib_name, sym_name):
yield API(name), ih.address
if sym_name.startswith("_"):
for name in capa.features.extractors.helpers.generate_symbols(lib_name, sym_name[1:]):
for name in capa.features.extractors.helpers.generate_symbols(lib_name, sym_name):
yield API(name), ih.address
if sym_name.startswith("_"):
for name in capa.features.extractors.helpers.generate_symbols(lib_name, sym_name[1:]):
yield API(name), ih.address
def extract_insn_number_features(
fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle
) -> Iterator[Tuple[Feature, Address]]:
) -> Iterator[tuple[Feature, Address]]:
"""
parse instruction number features
example:
@@ -127,7 +123,7 @@ def extract_insn_number_features(
"""
func: Function = fh.inner
results: List[Tuple[Any[Number, OperandNumber], Address]] = []
results: list[tuple[Any[Number, OperandNumber], Address]] = []
def llil_checker(il: LowLevelILInstruction, parent: LowLevelILInstruction, index: int) -> bool:
if il.operation == LowLevelILOperation.LLIL_LOAD:
@@ -158,7 +154,7 @@ def extract_insn_number_features(
yield from results
def extract_insn_bytes_features(fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle) -> Iterator[Tuple[Feature, Address]]:
def extract_insn_bytes_features(fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle) -> Iterator[tuple[Feature, Address]]:
"""
parse referenced byte sequences
example:
@@ -205,7 +201,7 @@ def extract_insn_bytes_features(fh: FunctionHandle, bbh: BBHandle, ih: InsnHandl
def extract_insn_string_features(
fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle
) -> Iterator[Tuple[Feature, Address]]:
) -> Iterator[tuple[Feature, Address]]:
"""
parse instruction string features
@@ -262,7 +258,7 @@ def extract_insn_string_features(
def extract_insn_offset_features(
fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle
) -> Iterator[Tuple[Feature, Address]]:
) -> Iterator[tuple[Feature, Address]]:
"""
parse instruction structure offset features
@@ -271,7 +267,7 @@ def extract_insn_offset_features(
"""
func: Function = fh.inner
results: List[Tuple[Any[Offset, OperandOffset], Address]] = []
results: list[tuple[Any[Offset, OperandOffset], Address]] = []
address_size = func.view.arch.address_size * 8
def llil_checker(il: LowLevelILInstruction, parent: LowLevelILInstruction, index: int) -> bool:
@@ -349,7 +345,7 @@ def is_nzxor_stack_cookie(f: Function, bb: BinjaBasicBlock, llil: LowLevelILInst
def extract_insn_nzxor_characteristic_features(
fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle
) -> Iterator[Tuple[Feature, Address]]:
) -> Iterator[tuple[Feature, Address]]:
"""
parse instruction non-zeroing XOR instruction
ignore expected non-zeroing XORs, e.g. security cookies
@@ -363,7 +359,7 @@ def extract_insn_nzxor_characteristic_features(
# e.g., <llil: eax = 0>, (LLIL_SET_REG). So we do not need to check whether the two operands are the same.
if il.operation == LowLevelILOperation.LLIL_XOR:
# Exclude cases related to the stack cookie
if is_nzxor_stack_cookie(fh.inner, bbh.inner[0], il):
if is_nzxor_stack_cookie(fh.inner, bbh.inner, il):
return False
results.append((Characteristic("nzxor"), ih.address))
return False
@@ -378,7 +374,7 @@ def extract_insn_nzxor_characteristic_features(
def extract_insn_mnemonic_features(
fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle
) -> Iterator[Tuple[Feature, Address]]:
) -> Iterator[tuple[Feature, Address]]:
"""parse instruction mnemonic features"""
insn: DisassemblyInstruction = ih.inner
yield Mnemonic(insn.text[0].text), ih.address
@@ -386,7 +382,7 @@ def extract_insn_mnemonic_features(
def extract_insn_obfs_call_plus_5_characteristic_features(
fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle
) -> Iterator[Tuple[Feature, Address]]:
) -> Iterator[tuple[Feature, Address]]:
"""
parse call $+5 instruction from the given instruction.
"""
@@ -397,7 +393,7 @@ def extract_insn_obfs_call_plus_5_characteristic_features(
def extract_insn_peb_access_characteristic_features(
fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle
) -> Iterator[Tuple[Feature, Address]]:
) -> Iterator[tuple[Feature, Address]]:
"""parse instruction peb access
fs:[0x30] on x86, gs:[0x60] on x64
@@ -440,7 +436,7 @@ def extract_insn_peb_access_characteristic_features(
def extract_insn_segment_access_features(
fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle
) -> Iterator[Tuple[Feature, Address]]:
) -> Iterator[tuple[Feature, Address]]:
"""parse instruction fs or gs access"""
func: Function = fh.inner
@@ -467,7 +463,7 @@ def extract_insn_segment_access_features(
def extract_insn_cross_section_cflow(
fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle
) -> Iterator[Tuple[Feature, Address]]:
) -> Iterator[tuple[Feature, Address]]:
"""inspect the instruction for a CALL or JMP that crosses section boundaries"""
func: Function = fh.inner
bv: BinaryView = func.view
@@ -487,7 +483,7 @@ def extract_insn_cross_section_cflow(
yield Characteristic("cross section flow"), ih.address
def extract_function_calls_from(fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle) -> Iterator[Tuple[Feature, Address]]:
def extract_function_calls_from(fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle) -> Iterator[tuple[Feature, Address]]:
"""extract functions calls from features
most relevant at the function scope, however, its most efficient to extract at the instruction scope
@@ -530,7 +526,7 @@ def extract_function_calls_from(fh: FunctionHandle, bbh: BBHandle, ih: InsnHandl
def extract_function_indirect_call_characteristic_features(
fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle
) -> Iterator[Tuple[Feature, Address]]:
) -> Iterator[tuple[Feature, Address]]:
"""extract indirect function calls (e.g., call eax or call dword ptr [edx+4])
does not include calls like => call ds:dword_ABD4974
@@ -558,7 +554,7 @@ def extract_function_indirect_call_characteristic_features(
yield Characteristic("indirect call"), ih.address
def extract_features(f: FunctionHandle, bbh: BBHandle, insn: InsnHandle) -> Iterator[Tuple[Feature, Address]]:
def extract_features(f: FunctionHandle, bbh: BBHandle, insn: InsnHandle) -> Iterator[tuple[Feature, Address]]:
"""extract instruction features"""
for inst_handler in INSTRUCTION_HANDLERS:
for feature, ea in inst_handler(f, bbh, insn):

View File

@@ -0,0 +1,64 @@
# Copyright (C) 2023 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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.
import logging
from typing import Iterator
import capa.features.extractors.helpers
from capa.helpers import assert_never
from capa.features.insn import API, Number
from capa.features.common import String, Feature
from capa.features.address import Address
from capa.features.extractors.cape.models import Call
from capa.features.extractors.base_extractor import CallHandle, ThreadHandle, ProcessHandle
logger = logging.getLogger(__name__)
def extract_call_features(ph: ProcessHandle, th: ThreadHandle, ch: CallHandle) -> Iterator[tuple[Feature, Address]]:
"""
this method extracts the given call's features (such as API name and arguments),
and returns them as API, Number, and String features.
args:
ph: process handle (for defining the extraction scope)
th: thread handle (for defining the extraction scope)
ch: call handle (for defining the extraction scope)
yields:
Feature, address; where Feature is either: API, Number, or String.
"""
call: Call = ch.inner
# list similar to disassembly: arguments right-to-left, call
for arg in reversed(call.arguments):
value = arg.value
if isinstance(value, list) and len(value) == 0:
# unsure why CAPE captures arguments as empty lists?
continue
elif isinstance(value, str):
yield String(value), ch.address
elif isinstance(value, int):
yield Number(value), ch.address
else:
assert_never(value)
for name in capa.features.extractors.helpers.generate_symbols("", call.api):
yield API(name), ch.address
def extract_features(ph: ProcessHandle, th: ThreadHandle, ch: CallHandle) -> Iterator[tuple[Feature, Address]]:
for handler in CALL_HANDLERS:
for feature, addr in handler(ph, th, ch):
yield feature, addr
CALL_HANDLERS = (extract_call_features,)

View File

@@ -0,0 +1,153 @@
# Copyright (C) 2023 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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.
import logging
from typing import Union, Iterator
import capa.features.extractors.cape.call
import capa.features.extractors.cape.file
import capa.features.extractors.cape.thread
import capa.features.extractors.cape.global_
import capa.features.extractors.cape.process
from capa.exceptions import EmptyReportError, UnsupportedFormatError
from capa.features.common import Feature, Characteristic
from capa.features.address import NO_ADDRESS, Address, AbsoluteVirtualAddress, _NoAddress
from capa.features.extractors.cape.models import Call, Static, Process, CapeReport
from capa.features.extractors.base_extractor import (
CallHandle,
SampleHashes,
ThreadHandle,
ProcessHandle,
DynamicFeatureExtractor,
)
logger = logging.getLogger(__name__)
TESTED_VERSIONS = {"2.2-CAPE", "2.4-CAPE"}
class CapeExtractor(DynamicFeatureExtractor):
def __init__(self, report: CapeReport):
super().__init__(
hashes=SampleHashes(
md5=report.target.file.md5.lower(),
sha1=report.target.file.sha1.lower(),
sha256=report.target.file.sha256.lower(),
)
)
self.report: CapeReport = report
# pre-compute these because we'll yield them at *every* scope.
self.global_features = list(capa.features.extractors.cape.global_.extract_features(self.report))
def get_base_address(self) -> Union[AbsoluteVirtualAddress, _NoAddress, None]:
# value according to the PE header, the actual trace may use a different imagebase
assert self.report.static is not None and self.report.static.pe is not None
return AbsoluteVirtualAddress(self.report.static.pe.imagebase)
def extract_global_features(self) -> Iterator[tuple[Feature, Address]]:
yield from self.global_features
def extract_file_features(self) -> Iterator[tuple[Feature, Address]]:
yield from capa.features.extractors.cape.file.extract_features(self.report)
def get_processes(self) -> Iterator[ProcessHandle]:
yield from capa.features.extractors.cape.file.get_processes(self.report)
def extract_process_features(self, ph: ProcessHandle) -> Iterator[tuple[Feature, Address]]:
yield from capa.features.extractors.cape.process.extract_features(ph)
def get_process_name(self, ph) -> str:
process: Process = ph.inner
return process.process_name
def get_threads(self, ph: ProcessHandle) -> Iterator[ThreadHandle]:
yield from capa.features.extractors.cape.process.get_threads(ph)
def extract_thread_features(self, ph: ProcessHandle, th: ThreadHandle) -> Iterator[tuple[Feature, Address]]:
if False:
# force this routine to be a generator,
# but we don't actually have any elements to generate.
yield Characteristic("never"), NO_ADDRESS
return
def get_calls(self, ph: ProcessHandle, th: ThreadHandle) -> Iterator[CallHandle]:
yield from capa.features.extractors.cape.thread.get_calls(ph, th)
def extract_call_features(
self, ph: ProcessHandle, th: ThreadHandle, ch: CallHandle
) -> Iterator[tuple[Feature, Address]]:
yield from capa.features.extractors.cape.call.extract_features(ph, th, ch)
def get_call_name(self, ph, th, ch) -> str:
call: Call = ch.inner
parts = []
parts.append(call.api)
parts.append("(")
for argument in call.arguments:
parts.append(argument.name)
parts.append("=")
if argument.pretty_value:
parts.append(argument.pretty_value)
else:
if isinstance(argument.value, int):
parts.append(hex(argument.value))
elif isinstance(argument.value, str):
parts.append('"')
parts.append(argument.value)
parts.append('"')
elif isinstance(argument.value, list):
pass
else:
capa.helpers.assert_never(argument.value)
parts.append(", ")
if call.arguments:
# remove the trailing comma
parts.pop()
parts.append(")")
parts.append(" -> ")
if call.pretty_return:
parts.append(call.pretty_return)
else:
parts.append(hex(call.return_))
return "".join(parts)
@classmethod
def from_report(cls, report: dict) -> "CapeExtractor":
cr = CapeReport.model_validate(report)
if cr.info.version not in TESTED_VERSIONS:
logger.warning("CAPE version '%s' not tested/supported yet", cr.info.version)
# TODO(mr-tz): support more file types
# https://github.com/mandiant/capa/issues/1933
if "PE" not in cr.target.file.type:
logger.error(
"capa currently only supports PE target files, this target file's type is: '%s'.\nPlease report this at: https://github.com/mandiant/capa/issues/1933",
cr.target.file.type,
)
# observed in 2.4-CAPE reports from capesandbox.com
if cr.static is None and cr.target.file.pe is not None:
cr.static = Static()
cr.static.pe = cr.target.file.pe
if cr.static is None:
raise UnsupportedFormatError("CAPE report missing static analysis")
if cr.static.pe is None:
raise UnsupportedFormatError("CAPE report missing PE analysis")
if len(cr.behavior.processes) == 0:
raise EmptyReportError("CAPE did not capture any processes")
return cls(cr)

View File

@@ -0,0 +1,132 @@
# Copyright (C) 2023 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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.
import logging
from typing import Iterator
from capa.features.file import Export, Import, Section
from capa.features.common import String, Feature
from capa.features.address import NO_ADDRESS, Address, ProcessAddress, AbsoluteVirtualAddress
from capa.features.extractors.helpers import generate_symbols
from capa.features.extractors.cape.models import CapeReport
from capa.features.extractors.base_extractor import ProcessHandle
logger = logging.getLogger(__name__)
def get_processes(report: CapeReport) -> Iterator[ProcessHandle]:
"""
get all the created processes for a sample
"""
seen_processes = {}
for process in report.behavior.processes:
addr = ProcessAddress(pid=process.process_id, ppid=process.parent_id)
yield ProcessHandle(address=addr, inner=process)
# check for pid and ppid reuse
if addr not in seen_processes:
seen_processes[addr] = [process]
else:
logger.warning(
"pid and ppid reuse detected between process %s and process%s: %s",
process,
"es" if len(seen_processes[addr]) > 1 else "",
seen_processes[addr],
)
seen_processes[addr].append(process)
def extract_import_names(report: CapeReport) -> Iterator[tuple[Feature, Address]]:
"""
extract imported function names
"""
assert report.static is not None and report.static.pe is not None
imports = report.static.pe.imports
if isinstance(imports, dict):
imports = list(imports.values())
assert isinstance(imports, list)
for library in imports:
for function in library.imports:
if not function.name:
continue
for name in generate_symbols(library.dll, function.name, include_dll=True):
yield Import(name), AbsoluteVirtualAddress(function.address)
def extract_export_names(report: CapeReport) -> Iterator[tuple[Feature, Address]]:
assert report.static is not None and report.static.pe is not None
for function in report.static.pe.exports:
yield Export(function.name), AbsoluteVirtualAddress(function.address)
def extract_section_names(report: CapeReport) -> Iterator[tuple[Feature, Address]]:
assert report.static is not None and report.static.pe is not None
for section in report.static.pe.sections:
yield Section(section.name), AbsoluteVirtualAddress(section.virtual_address)
def extract_file_strings(report: CapeReport) -> Iterator[tuple[Feature, Address]]:
if report.strings is not None:
for string in report.strings:
yield String(string), NO_ADDRESS
def extract_used_regkeys(report: CapeReport) -> Iterator[tuple[Feature, Address]]:
for regkey in report.behavior.summary.keys:
yield String(regkey), NO_ADDRESS
def extract_used_files(report: CapeReport) -> Iterator[tuple[Feature, Address]]:
for file in report.behavior.summary.files:
yield String(file), NO_ADDRESS
def extract_used_mutexes(report: CapeReport) -> Iterator[tuple[Feature, Address]]:
for mutex in report.behavior.summary.mutexes:
yield String(mutex), NO_ADDRESS
def extract_used_commands(report: CapeReport) -> Iterator[tuple[Feature, Address]]:
for cmd in report.behavior.summary.executed_commands:
yield String(cmd), NO_ADDRESS
def extract_used_apis(report: CapeReport) -> Iterator[tuple[Feature, Address]]:
for symbol in report.behavior.summary.resolved_apis:
yield String(symbol), NO_ADDRESS
def extract_used_services(report: CapeReport) -> Iterator[tuple[Feature, Address]]:
for svc in report.behavior.summary.created_services:
yield String(svc), NO_ADDRESS
for svc in report.behavior.summary.started_services:
yield String(svc), NO_ADDRESS
def extract_features(report: CapeReport) -> Iterator[tuple[Feature, Address]]:
for handler in FILE_HANDLERS:
for feature, addr in handler(report):
yield feature, addr
FILE_HANDLERS = (
extract_import_names,
extract_export_names,
extract_section_names,
extract_file_strings,
extract_used_regkeys,
extract_used_files,
extract_used_mutexes,
extract_used_commands,
extract_used_apis,
extract_used_services,
)

View File

@@ -0,0 +1,93 @@
# Copyright (C) 2023 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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.
import logging
from typing import Iterator
from capa.features.common import (
OS,
OS_ANY,
OS_LINUX,
ARCH_I386,
FORMAT_PE,
ARCH_AMD64,
FORMAT_ELF,
OS_WINDOWS,
Arch,
Format,
Feature,
)
from capa.features.address import NO_ADDRESS, Address
from capa.features.extractors.cape.models import CapeReport
logger = logging.getLogger(__name__)
def extract_arch(report: CapeReport) -> Iterator[tuple[Feature, Address]]:
if "Intel 80386" in report.target.file.type:
yield Arch(ARCH_I386), NO_ADDRESS
elif "x86-64" in report.target.file.type:
yield Arch(ARCH_AMD64), NO_ADDRESS
else:
logger.warning("unrecognized Architecture: %s", report.target.file.type)
raise ValueError(
f"unrecognized Architecture from the CAPE report; output of file command: {report.target.file.type}"
)
def extract_format(report: CapeReport) -> Iterator[tuple[Feature, Address]]:
if "PE" in report.target.file.type:
yield Format(FORMAT_PE), NO_ADDRESS
elif "ELF" in report.target.file.type:
yield Format(FORMAT_ELF), NO_ADDRESS
else:
logger.warning("unknown file format, file command output: %s", report.target.file.type)
raise ValueError(
f"unrecognized file format from the CAPE report; output of file command: {report.target.file.type}"
)
def extract_os(report: CapeReport) -> Iterator[tuple[Feature, Address]]:
# this variable contains the output of the file command
file_output = report.target.file.type
if "windows" in file_output.lower():
yield OS(OS_WINDOWS), NO_ADDRESS
elif "elf" in file_output.lower():
# operating systems recognized by the file command: https://github.com/file/file/blob/master/src/readelf.c#L609
if "Linux" in file_output:
yield OS(OS_LINUX), NO_ADDRESS
elif "Hurd" in file_output:
yield OS("hurd"), NO_ADDRESS
elif "Solaris" in file_output:
yield OS("solaris"), NO_ADDRESS
elif "kFreeBSD" in file_output:
yield OS("freebsd"), NO_ADDRESS
elif "kNetBSD" in file_output:
yield OS("netbsd"), NO_ADDRESS
else:
# if the operating system information is missing from the cape report, it's likely a bug
logger.warning("unrecognized OS: %s", file_output)
raise ValueError(f"unrecognized OS from the CAPE report; output of file command: {file_output}")
else:
# the sample is shellcode
logger.debug("unsupported file format, file command output: %s", file_output)
yield OS(OS_ANY), NO_ADDRESS
def extract_features(report: CapeReport) -> Iterator[tuple[Feature, Address]]:
for global_handler in GLOBAL_HANDLER:
for feature, addr in global_handler(report):
yield feature, addr
GLOBAL_HANDLER = (
extract_format,
extract_os,
extract_arch,
)

View File

@@ -0,0 +1,29 @@
# Copyright (C) 2023 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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.
from typing import Any
from capa.features.extractors.base_extractor import ProcessHandle
def find_process(processes: list[dict[str, Any]], ph: ProcessHandle) -> dict[str, Any]:
"""
find a specific process identified by a process handler.
args:
processes: a list of processes extracted by CAPE
ph: handle of the sought process
return:
a CAPE-defined dictionary for the sought process' information
"""
for process in processes:
if ph.address.ppid == process["parent_id"] and ph.address.pid == process["process_id"]:
return process
return {}

View File

@@ -0,0 +1,448 @@
# Copyright (C) 2023 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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.
import binascii
from typing import Any, Union, Literal, Optional, Annotated, TypeAlias
from pydantic import Field, BaseModel, ConfigDict
from pydantic.functional_validators import BeforeValidator
def validate_hex_int(value):
if isinstance(value, str):
return int(value, 16) if value.startswith("0x") else int(value, 10)
else:
return value
def validate_hex_bytes(value):
return binascii.unhexlify(value) if isinstance(value, str) else value
HexInt = Annotated[int, BeforeValidator(validate_hex_int)]
HexBytes = Annotated[bytes, BeforeValidator(validate_hex_bytes)]
# a model that *cannot* have extra fields
# if they do, pydantic raises an exception.
# use this for models we rely upon and cannot change.
#
# for things that may be extended and we don't care,
# use FlexibleModel.
class ExactModel(BaseModel):
model_config = ConfigDict(extra="forbid")
# a model that can have extra fields that we ignore.
# use this if we don't want to raise an exception for extra
# data fields that we didn't expect.
class FlexibleModel(BaseModel):
pass
# use this type to indicate that we won't model this data.
# because it's not relevant to our use in capa.
#
# while its nice to have full coverage of the data shape,
# it can easily change and break our parsing.
# so we really only want to describe what we'll use.
Skip: TypeAlias = Optional[Any]
# mark fields that we haven't seen yet and need to model.
# pydantic should raise an error when encountering data
# in a field with this type.
# then we can update the model with the discovered shape.
TODO: TypeAlias = None
ListTODO: TypeAlias = list[None]
DictTODO: TypeAlias = ExactModel
Emptydict: TypeAlias = BaseModel
EmptyList: TypeAlias = list[Any]
class Info(FlexibleModel):
version: str
class ImportedSymbol(ExactModel):
address: HexInt
name: Optional[str] = None
class ImportedDll(ExactModel):
dll: str
imports: list[ImportedSymbol]
class DirectoryEntry(ExactModel):
name: str
virtual_address: HexInt
size: HexInt
class Section(ExactModel):
name: str
raw_address: HexInt
virtual_address: HexInt
virtual_size: HexInt
size_of_data: HexInt
characteristics: str
characteristics_raw: HexInt
entropy: float
class Resource(ExactModel):
name: str
language: Optional[str] = None
sublanguage: str
filetype: Optional[str]
offset: HexInt
size: HexInt
entropy: float
class DigitalSigner(FlexibleModel):
md5_fingerprint: str
not_after: str
not_before: str
serial_number: str
sha1_fingerprint: str
sha256_fingerprint: str
issuer_commonName: Optional[str] = None
issuer_countryName: Optional[str] = None
issuer_localityName: Optional[str] = None
issuer_organizationName: Optional[str] = None
issuer_stateOrProvinceName: Optional[str] = None
subject_commonName: Optional[str] = None
subject_countryName: Optional[str] = None
subject_localityName: Optional[str] = None
subject_organizationName: Optional[str] = None
subject_stateOrProvinceName: Optional[str] = None
extensions_authorityInfoAccess_caIssuers: Optional[str] = None
extensions_authorityKeyIdentifier: Optional[str] = None
extensions_cRLDistributionPoints_0: Optional[str] = None
extensions_certificatePolicies_0: Optional[str] = None
extensions_subjectAltName_0: Optional[str] = None
extensions_subjectKeyIdentifier: Optional[str] = None
class AuxSigner(ExactModel):
name: str
issued_to: str = Field(alias="Issued to")
issued_by: str = Field(alias="Issued by")
expires: str = Field(alias="Expires")
sha1_hash: str = Field(alias="SHA1 hash")
class Signer(ExactModel):
aux_sha1: Optional[str] = None
aux_timestamp: Optional[str] = None
aux_valid: Optional[bool] = None
aux_error: Optional[bool] = None
aux_error_desc: Optional[str] = None
aux_signers: Optional[list[AuxSigner]] = None
class Overlay(ExactModel):
offset: HexInt
size: HexInt
class KV(ExactModel):
name: str
value: str
class ExportedSymbol(ExactModel):
address: HexInt
name: str
ordinal: int
class PE(ExactModel):
peid_signatures: TODO
imagebase: HexInt
entrypoint: HexInt
reported_checksum: HexInt
actual_checksum: HexInt
osversion: str
pdbpath: Optional[str] = None
timestamp: str
# list[ImportedDll], or dict[basename(dll), ImportedDll]
imports: Union[list[ImportedDll], dict[str, ImportedDll]]
imported_dll_count: Optional[int] = None
imphash: str
exported_dll_name: Optional[str] = None
exports: list[ExportedSymbol]
dirents: list[DirectoryEntry]
sections: list[Section]
ep_bytes: Optional[HexBytes] = None
overlay: Optional[Overlay] = None
resources: list[Resource]
versioninfo: list[KV]
# base64 encoded data
icon: Optional[str] = None
# MD5-like hash
icon_hash: Optional[str] = None
# MD5-like hash
icon_fuzzy: Optional[str] = None
# short hex string
icon_dhash: Optional[str] = None
digital_signers: list[DigitalSigner]
guest_signers: Signer
# TODO(mr-tz): target.file.dotnet, target.file.extracted_files, target.file.extracted_files_tool,
# target.file.extracted_files_time
# https://github.com/mandiant/capa/issues/1814
class File(FlexibleModel):
type: str
cape_type_code: Optional[int] = None
cape_type: Optional[str] = None
pid: Optional[Union[int, Literal[""]]] = None
name: Union[list[str], str]
path: str
guest_paths: Union[list[str], str, None]
timestamp: Optional[str] = None
#
# hashes
#
crc32: str
md5: str
sha1: str
sha256: str
sha512: str
sha3_384: Optional[str] = None
ssdeep: str
# unsure why this would ever be "False"
tlsh: Optional[Union[str, bool]] = None
rh_hash: Optional[str] = None
#
# other metadata, static analysis
#
size: int
pe: Optional[PE] = None
ep_bytes: Optional[HexBytes] = None
entrypoint: Optional[int] = None
data: Optional[str] = None
strings: Optional[list[str]] = None
#
# detections (skip)
#
yara: Skip = None
cape_yara: Skip = None
clamav: Skip = None
virustotal: Skip = None
class ProcessFile(File):
#
# like a File, but also has dynamic analysis results
#
pid: Optional[int] = None
process_path: Optional[str] = None
process_name: Optional[str] = None
module_path: Optional[str] = None
virtual_address: Optional[HexInt] = None
target_pid: Optional[Union[int, str]] = None
target_path: Optional[str] = None
target_process: Optional[str] = None
class Argument(ExactModel):
name: str
# unsure why empty list is provided here
value: Union[HexInt, int, str, EmptyList]
pretty_value: Optional[str] = None
class Call(ExactModel):
timestamp: str
thread_id: int
category: str
api: str
arguments: list[Argument]
status: bool
return_: HexInt = Field(alias="return")
pretty_return: Optional[str] = None
repeated: int
# virtual addresses
caller: HexInt
parentcaller: HexInt
# index into calls array
id: int
# FlexibleModel to account for extended fields
# refs: https://github.com/mandiant/capa/issues/2466
# https://github.com/kevoreilly/CAPEv2/pull/2199
class Process(FlexibleModel):
process_id: int
process_name: str
parent_id: int
module_path: str
first_seen: str
calls: list[Call]
threads: list[int]
environ: dict[str, str]
class ProcessTree(ExactModel):
name: str
pid: int
parent_id: int
module_path: str
threads: list[int]
environ: dict[str, str]
children: list["ProcessTree"]
class Summary(ExactModel):
files: list[str]
read_files: list[str]
write_files: list[str]
delete_files: list[str]
keys: list[str]
read_keys: list[str]
write_keys: list[str]
delete_keys: list[str]
executed_commands: list[str]
resolved_apis: list[str]
mutexes: list[str]
created_services: list[str]
started_services: list[str]
class EncryptedBuffer(ExactModel):
process_name: str
pid: int
api_call: str
buffer: str
buffer_size: Optional[int] = None
crypt_key: Optional[Union[HexInt, str]] = None
class Behavior(ExactModel):
summary: Summary
# list of processes, of threads, of calls
processes: list[Process]
# tree of processes
processtree: list[ProcessTree]
anomaly: list[str]
encryptedbuffers: list[EncryptedBuffer]
# these are small objects that describe atomic events,
# like file move, registry access.
# we'll detect the same with our API call analysis.
enhanced: Skip = None
class Target(ExactModel):
category: str
file: File
pe: Optional[PE] = None
class Static(ExactModel):
pe: Optional[PE] = None
flare_capa: Skip = None
class Cape(ExactModel):
payloads: list[ProcessFile]
configs: Skip = None
# flexible because there may be more sorts of analysis
# but we only care about the ones described here.
class CapeReport(FlexibleModel):
# the input file, I think
target: Target
# info about the processing job, like machine and distributed metadata.
info: Info
#
# static analysis results
#
static: Optional[Static] = None
strings: Optional[list[str]] = None
#
# dynamic analysis results
#
# post-processed results: process tree, anomalies, etc
behavior: Behavior
# post-processed results: payloads and extracted configs
CAPE: Optional[Union[Cape, list]] = None
dropped: Optional[list[File]] = None
procdump: Optional[list[ProcessFile]] = None
procmemory: Optional[ListTODO] = None
# =========================================================================
# information we won't use in capa
#
#
# NBIs and HBIs
# these are super interesting, but they don't enable use to detect behaviors.
# they take a lot of code to model and details to maintain.
#
# if we come up with a future use for this, go ahead and re-enable!
#
network: Skip = None
suricata: Skip = None
curtain: Skip = None
sysmon: Skip = None
url_analysis: Skip = None
# screenshot hash values
deduplicated_shots: Skip = None
# k-v pairs describing the time it took to run each stage.
statistics: Skip = None
# k-v pairs of ATT&CK ID to signature name or similar.
ttps: Skip = None
# debug log messages
debug: Skip = None
# various signature matches
# we could potentially extend capa to use this info one day,
# though it would be quite sandbox-specific,
# and more detection-oriented than capability detection.
signatures: Skip = None
malfamily_tag: Optional[str] = None
malscore: float
detections: Skip = None
detections2pid: Optional[dict[int, list[str]]] = None
# AV detections for the sample.
virustotal: Skip = None
@classmethod
def from_buf(cls, buf: bytes) -> "CapeReport":
return cls.model_validate_json(buf)

View File

@@ -0,0 +1,48 @@
# Copyright (C) 2023 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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.
import logging
from typing import Iterator
from capa.features.common import String, Feature
from capa.features.address import Address, ThreadAddress
from capa.features.extractors.cape.models import Process
from capa.features.extractors.base_extractor import ThreadHandle, ProcessHandle
logger = logging.getLogger(__name__)
def get_threads(ph: ProcessHandle) -> Iterator[ThreadHandle]:
"""
get the threads associated with a given process
"""
process: Process = ph.inner
threads: list[int] = process.threads
for thread in threads:
address: ThreadAddress = ThreadAddress(process=ph.address, tid=thread)
yield ThreadHandle(address=address, inner={})
def extract_environ_strings(ph: ProcessHandle) -> Iterator[tuple[Feature, Address]]:
"""
extract strings from a process' provided environment variables.
"""
process: Process = ph.inner
for value in (value for value in process.environ.values() if value):
yield String(value), ph.address
def extract_features(ph: ProcessHandle) -> Iterator[tuple[Feature, Address]]:
for handler in PROCESS_HANDLERS:
for feature, addr in handler(ph):
yield feature, addr
PROCESS_HANDLERS = (extract_environ_strings,)

View File

@@ -0,0 +1,32 @@
# Copyright (C) 2023 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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.
import logging
from typing import Iterator
from capa.features.address import DynamicCallAddress
from capa.features.extractors.helpers import generate_symbols
from capa.features.extractors.cape.models import Process
from capa.features.extractors.base_extractor import CallHandle, ThreadHandle, ProcessHandle
logger = logging.getLogger(__name__)
def get_calls(ph: ProcessHandle, th: ThreadHandle) -> Iterator[CallHandle]:
process: Process = ph.inner
tid = th.address.tid
for call_index, call in enumerate(process.calls):
if call.thread_id != tid:
continue
for symbol in generate_symbols("", call.api):
call.api = symbol
addr = DynamicCallAddress(thread=th.address, id=call_index)
yield CallHandle(address=addr, inner=call)

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2023 Mandiant, Inc. All Rights Reserved.
# Copyright (C) 2021 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
@@ -6,10 +6,11 @@
# 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.
import io
import re
import logging
import binascii
import contextlib
from typing import Tuple, Iterator
from typing import Iterator
import pefile
@@ -41,9 +42,10 @@ logger = logging.getLogger(__name__)
MATCH_PE = b"MZ"
MATCH_ELF = b"\x7fELF"
MATCH_RESULT = b'{"meta":'
MATCH_JSON_OBJECT = b'{"'
def extract_file_strings(buf, **kwargs) -> Iterator[Tuple[String, Address]]:
def extract_file_strings(buf: bytes, **kwargs) -> Iterator[tuple[String, Address]]:
"""
extract ASCII and UTF-16 LE strings from file
"""
@@ -54,7 +56,7 @@ def extract_file_strings(buf, **kwargs) -> Iterator[Tuple[String, Address]]:
yield String(s.s), FileOffsetAddress(s.offset)
def extract_format(buf) -> Iterator[Tuple[Feature, Address]]:
def extract_format(buf: bytes) -> Iterator[tuple[Feature, Address]]:
if buf.startswith(MATCH_PE):
yield Format(FORMAT_PE), NO_ADDRESS
elif buf.startswith(MATCH_ELF):
@@ -63,16 +65,21 @@ def extract_format(buf) -> Iterator[Tuple[Feature, Address]]:
yield Format(FORMAT_FREEZE), NO_ADDRESS
elif buf.startswith(MATCH_RESULT):
yield Format(FORMAT_RESULT), NO_ADDRESS
elif re.sub(rb"\s", b"", buf[:20]).startswith(MATCH_JSON_OBJECT):
# potential start of JSON object data without whitespace
# we don't know what it is exactly, but may support it (e.g. a dynamic CAPE sandbox report)
# skip verdict here and let subsequent code analyze this further
return
else:
# we likely end up here:
# 1. handling a file format (e.g. macho)
#
# for (1), this logic will need to be updated as the format is implemented.
logger.debug("unsupported file format: %s", binascii.hexlify(buf[:4]).decode("ascii"))
logger.debug("unknown file format: %s", buf[:4].hex())
return
def extract_arch(buf) -> Iterator[Tuple[Feature, Address]]:
def extract_arch(buf) -> Iterator[tuple[Feature, Address]]:
if buf.startswith(MATCH_PE):
yield from capa.features.extractors.pefile.extract_file_arch(pe=pefile.PE(data=buf))
@@ -104,7 +111,7 @@ def extract_arch(buf) -> Iterator[Tuple[Feature, Address]]:
return
def extract_os(buf, os=OS_AUTO) -> Iterator[Tuple[Feature, Address]]:
def extract_os(buf, os=OS_AUTO) -> Iterator[tuple[Feature, Address]]:
if os != OS_AUTO:
yield OS(os), NO_ADDRESS

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2023 Mandiant, Inc. All Rights Reserved.
# Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
@@ -8,7 +8,7 @@
from __future__ import annotations
from typing import Dict, List, Tuple, Union, Iterator, Optional
from typing import Union, Iterator, Optional
from pathlib import Path
import dnfile
@@ -22,7 +22,13 @@ import capa.features.extractors.dnfile.function
from capa.features.common import Feature
from capa.features.address import NO_ADDRESS, Address, DNTokenAddress, DNTokenOffsetAddress
from capa.features.extractors.dnfile.types import DnType, DnUnmanagedMethod
from capa.features.extractors.base_extractor import BBHandle, InsnHandle, FunctionHandle, FeatureExtractor
from capa.features.extractors.base_extractor import (
BBHandle,
InsnHandle,
SampleHashes,
FunctionHandle,
StaticFeatureExtractor,
)
from capa.features.extractors.dnfile.helpers import (
get_dotnet_types,
get_dotnet_fields,
@@ -35,11 +41,11 @@ from capa.features.extractors.dnfile.helpers import (
class DnFileFeatureExtractorCache:
def __init__(self, pe: dnfile.dnPE):
self.imports: Dict[int, Union[DnType, DnUnmanagedMethod]] = {}
self.native_imports: Dict[int, Union[DnType, DnUnmanagedMethod]] = {}
self.methods: Dict[int, Union[DnType, DnUnmanagedMethod]] = {}
self.fields: Dict[int, Union[DnType, DnUnmanagedMethod]] = {}
self.types: Dict[int, Union[DnType, DnUnmanagedMethod]] = {}
self.imports: dict[int, Union[DnType, DnUnmanagedMethod]] = {}
self.native_imports: dict[int, Union[DnType, DnUnmanagedMethod]] = {}
self.methods: dict[int, Union[DnType, DnUnmanagedMethod]] = {}
self.fields: dict[int, Union[DnType, DnUnmanagedMethod]] = {}
self.types: dict[int, Union[DnType, DnUnmanagedMethod]] = {}
for import_ in get_dotnet_managed_imports(pe):
self.imports[import_.token] = import_
@@ -68,17 +74,17 @@ class DnFileFeatureExtractorCache:
return self.types.get(token)
class DnfileFeatureExtractor(FeatureExtractor):
class DnfileFeatureExtractor(StaticFeatureExtractor):
def __init__(self, path: Path):
super().__init__()
self.pe: dnfile.dnPE = dnfile.dnPE(str(path))
super().__init__(hashes=SampleHashes.from_bytes(path.read_bytes()))
# pre-compute .NET token lookup tables; each .NET method has access to this cache for feature extraction
# most relevant at instruction scope
self.token_cache: DnFileFeatureExtractorCache = DnFileFeatureExtractorCache(self.pe)
# pre-compute these because we'll yield them at *every* scope.
self.global_features: List[Tuple[Feature, Address]] = []
self.global_features: list[tuple[Feature, Address]] = []
self.global_features.extend(capa.features.extractors.dotnetfile.extract_file_format())
self.global_features.extend(capa.features.extractors.dotnetfile.extract_file_os(pe=self.pe))
self.global_features.extend(capa.features.extractors.dotnetfile.extract_file_arch(pe=self.pe))
@@ -94,7 +100,7 @@ class DnfileFeatureExtractor(FeatureExtractor):
def get_functions(self) -> Iterator[FunctionHandle]:
# create a method lookup table
methods: Dict[Address, FunctionHandle] = {}
methods: dict[Address, FunctionHandle] = {}
for token, method in get_dotnet_managed_method_bodies(self.pe):
fh: FunctionHandle = FunctionHandle(
address=DNTokenAddress(token),
@@ -130,7 +136,7 @@ class DnfileFeatureExtractor(FeatureExtractor):
yield from methods.values()
def extract_function_features(self, fh) -> Iterator[Tuple[Feature, Address]]:
def extract_function_features(self, fh) -> Iterator[tuple[Feature, Address]]:
yield from capa.features.extractors.dnfile.function.extract_features(fh)
def get_basic_blocks(self, f) -> Iterator[BBHandle]:
@@ -151,5 +157,5 @@ class DnfileFeatureExtractor(FeatureExtractor):
inner=insn,
)
def extract_insn_features(self, fh, bbh, ih) -> Iterator[Tuple[Feature, Address]]:
def extract_insn_features(self, fh, bbh, ih) -> Iterator[tuple[Feature, Address]]:
yield from capa.features.extractors.dnfile.insn.extract_features(fh, bbh, ih)

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2023 Mandiant, Inc. All Rights Reserved.
# Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
@@ -8,7 +8,7 @@
from __future__ import annotations
from typing import Tuple, Iterator
from typing import Iterator
import dnfile
@@ -18,35 +18,35 @@ from capa.features.common import Class, Format, String, Feature, Namespace, Char
from capa.features.address import Address
def extract_file_import_names(pe: dnfile.dnPE) -> Iterator[Tuple[Import, Address]]:
def extract_file_import_names(pe: dnfile.dnPE) -> Iterator[tuple[Import, Address]]:
yield from capa.features.extractors.dotnetfile.extract_file_import_names(pe=pe)
def extract_file_format(pe: dnfile.dnPE) -> Iterator[Tuple[Format, Address]]:
def extract_file_format(pe: dnfile.dnPE) -> Iterator[tuple[Format, Address]]:
yield from capa.features.extractors.dotnetfile.extract_file_format(pe=pe)
def extract_file_function_names(pe: dnfile.dnPE) -> Iterator[Tuple[FunctionName, Address]]:
def extract_file_function_names(pe: dnfile.dnPE) -> Iterator[tuple[FunctionName, Address]]:
yield from capa.features.extractors.dotnetfile.extract_file_function_names(pe=pe)
def extract_file_strings(pe: dnfile.dnPE) -> Iterator[Tuple[String, Address]]:
def extract_file_strings(pe: dnfile.dnPE) -> Iterator[tuple[String, Address]]:
yield from capa.features.extractors.dotnetfile.extract_file_strings(pe=pe)
def extract_file_mixed_mode_characteristic_features(pe: dnfile.dnPE) -> Iterator[Tuple[Characteristic, Address]]:
def extract_file_mixed_mode_characteristic_features(pe: dnfile.dnPE) -> Iterator[tuple[Characteristic, Address]]:
yield from capa.features.extractors.dotnetfile.extract_file_mixed_mode_characteristic_features(pe=pe)
def extract_file_namespace_features(pe: dnfile.dnPE) -> Iterator[Tuple[Namespace, Address]]:
def extract_file_namespace_features(pe: dnfile.dnPE) -> Iterator[tuple[Namespace, Address]]:
yield from capa.features.extractors.dotnetfile.extract_file_namespace_features(pe=pe)
def extract_file_class_features(pe: dnfile.dnPE) -> Iterator[Tuple[Class, Address]]:
def extract_file_class_features(pe: dnfile.dnPE) -> Iterator[tuple[Class, Address]]:
yield from capa.features.extractors.dotnetfile.extract_file_class_features(pe=pe)
def extract_features(pe: dnfile.dnPE) -> Iterator[Tuple[Feature, Address]]:
def extract_features(pe: dnfile.dnPE) -> Iterator[tuple[Feature, Address]]:
for file_handler in FILE_HANDLERS:
for feature, address in file_handler(pe):
yield feature, address

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2023 Mandiant, Inc. All Rights Reserved.
# Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
@@ -9,7 +9,7 @@
from __future__ import annotations
import logging
from typing import Tuple, Iterator
from typing import Iterator
from capa.features.common import Feature, Characteristic
from capa.features.address import Address
@@ -18,30 +18,30 @@ from capa.features.extractors.base_extractor import FunctionHandle
logger = logging.getLogger(__name__)
def extract_function_calls_to(fh: FunctionHandle) -> Iterator[Tuple[Characteristic, Address]]:
def extract_function_calls_to(fh: FunctionHandle) -> Iterator[tuple[Characteristic, Address]]:
"""extract callers to a function"""
for dest in fh.ctx["calls_to"]:
yield Characteristic("calls to"), dest
def extract_function_calls_from(fh: FunctionHandle) -> Iterator[Tuple[Characteristic, Address]]:
def extract_function_calls_from(fh: FunctionHandle) -> Iterator[tuple[Characteristic, Address]]:
"""extract callers from a function"""
for src in fh.ctx["calls_from"]:
yield Characteristic("calls from"), src
def extract_recursive_call(fh: FunctionHandle) -> Iterator[Tuple[Characteristic, Address]]:
def extract_recursive_call(fh: FunctionHandle) -> Iterator[tuple[Characteristic, Address]]:
"""extract recursive function call"""
if fh.address in fh.ctx["calls_to"]:
yield Characteristic("recursive call"), fh.address
def extract_function_loop(fh: FunctionHandle) -> Iterator[Tuple[Characteristic, Address]]:
def extract_function_loop(fh: FunctionHandle) -> Iterator[tuple[Characteristic, Address]]:
"""extract loop indicators from a function"""
raise NotImplementedError()
def extract_features(fh: FunctionHandle) -> Iterator[Tuple[Feature, Address]]:
def extract_features(fh: FunctionHandle) -> Iterator[tuple[Feature, Address]]:
for func_handler in FUNCTION_HANDLERS:
for feature, addr in func_handler(fh):
yield feature, addr

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2023 Mandiant, Inc. All Rights Reserved.
# Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
@@ -9,7 +9,7 @@
from __future__ import annotations
import logging
from typing import Dict, Tuple, Union, Iterator, Optional
from typing import Union, Iterator, Optional
import dnfile
from dncil.cil.body import CilMethodBody
@@ -83,7 +83,7 @@ def read_dotnet_user_string(pe: dnfile.dnPE, token: StringToken) -> Optional[str
return None
try:
user_string: Optional[dnfile.stream.UserString] = pe.net.user_strings.get_us(token.rid)
user_string: Optional[dnfile.stream.UserString] = pe.net.user_strings.get(token.rid)
except UnicodeDecodeError as e:
logger.debug("failed to decode #US stream index 0x%08x (%s)", token.rid, e)
return None
@@ -119,28 +119,32 @@ def get_dotnet_managed_imports(pe: dnfile.dnPE) -> Iterator[DnType]:
access: Optional[str]
# assume .NET imports starting with get_/set_ are used to access a property
if member_ref.Name.startswith("get_"):
member_ref_name: str = str(member_ref.Name)
if member_ref_name.startswith("get_"):
access = FeatureAccess.READ
elif member_ref.Name.startswith("set_"):
elif member_ref_name.startswith("set_"):
access = FeatureAccess.WRITE
else:
access = None
member_ref_name: str = member_ref.Name
if member_ref_name.startswith(("get_", "set_")):
# remove get_/set_ from MemberRef name
member_ref_name = member_ref_name[4:]
typerefnamespace, typerefname = resolve_nested_typeref_name(
member_ref.Class.row_index, member_ref.Class.row, pe
)
yield DnType(
token,
member_ref.Class.row.TypeName,
namespace=member_ref.Class.row.TypeNamespace,
typerefname,
namespace=typerefnamespace,
member=member_ref_name,
access=access,
)
def get_dotnet_methoddef_property_accessors(pe: dnfile.dnPE) -> Iterator[Tuple[int, str]]:
def get_dotnet_methoddef_property_accessors(pe: dnfile.dnPE) -> Iterator[tuple[int, str]]:
"""get MethodDef methods used to access properties
see https://www.ntcore.com/files/dotnetformat.htm
@@ -188,7 +192,9 @@ def get_dotnet_managed_methods(pe: dnfile.dnPE) -> Iterator[DnType]:
TypeNamespace (index into String heap)
MethodList (index into MethodDef table; it marks the first of a contiguous run of Methods owned by this Type)
"""
accessor_map: Dict[int, str] = {}
nested_class_table = get_dotnet_nested_class_table_index(pe)
accessor_map: dict[int, str] = {}
for methoddef, methoddef_access in get_dotnet_methoddef_property_accessors(pe):
accessor_map[methoddef] = methoddef_access
@@ -206,12 +212,14 @@ def get_dotnet_managed_methods(pe: dnfile.dnPE) -> Iterator[DnType]:
token: int = calculate_dotnet_token_value(method.table.number, method.row_index)
access: Optional[str] = accessor_map.get(token)
method_name: str = method.row.Name
method_name: str = str(method.row.Name)
if method_name.startswith(("get_", "set_")):
# remove get_/set_
method_name = method_name[4:]
yield DnType(token, typedef.TypeName, namespace=typedef.TypeNamespace, member=method_name, access=access)
typedefnamespace, typedefname = resolve_nested_typedef_name(nested_class_table, rid, typedef, pe)
yield DnType(token, typedefname, namespace=typedefnamespace, member=method_name, access=access)
def get_dotnet_fields(pe: dnfile.dnPE) -> Iterator[DnType]:
@@ -225,6 +233,8 @@ def get_dotnet_fields(pe: dnfile.dnPE) -> Iterator[DnType]:
TypeNamespace (index into String heap)
FieldList (index into Field table; it marks the first of a contiguous run of Fields owned by this Type)
"""
nested_class_table = get_dotnet_nested_class_table_index(pe)
for rid, typedef in iter_dotnet_table(pe, dnfile.mdtable.TypeDef.number):
assert isinstance(typedef, dnfile.mdtable.TypeDefRow)
@@ -235,11 +245,14 @@ def get_dotnet_fields(pe: dnfile.dnPE) -> Iterator[DnType]:
if field.row is None:
logger.debug("TypeDef[0x%X] FieldList[0x%X] row is None", rid, idx)
continue
typedefnamespace, typedefname = resolve_nested_typedef_name(nested_class_table, rid, typedef, pe)
token: int = calculate_dotnet_token_value(field.table.number, field.row_index)
yield DnType(token, typedef.TypeName, namespace=typedef.TypeNamespace, member=field.row.Name)
yield DnType(token, typedefname, namespace=typedefnamespace, member=field.row.Name)
def get_dotnet_managed_method_bodies(pe: dnfile.dnPE) -> Iterator[Tuple[int, CilMethodBody]]:
def get_dotnet_managed_method_bodies(pe: dnfile.dnPE) -> Iterator[tuple[int, CilMethodBody]]:
"""get managed methods from MethodDef table"""
for rid, method_def in iter_dotnet_table(pe, dnfile.mdtable.MethodDef.number):
assert isinstance(method_def, dnfile.mdtable.MethodDefRow)
@@ -276,8 +289,8 @@ def get_dotnet_unmanaged_imports(pe: dnfile.dnPE) -> Iterator[DnUnmanagedMethod]
logger.debug("ImplMap[0x%X] ImportScope row is None", rid)
module = ""
else:
module = impl_map.ImportScope.row.Name
method: str = impl_map.ImportName
module = str(impl_map.ImportScope.row.Name)
method: str = str(impl_map.ImportName)
member_forward_table: int
if impl_map.MemberForwarded.table is None:
@@ -300,19 +313,122 @@ def get_dotnet_unmanaged_imports(pe: dnfile.dnPE) -> Iterator[DnUnmanagedMethod]
yield DnUnmanagedMethod(token, module, method)
def get_dotnet_table_row(pe: dnfile.dnPE, table_index: int, row_index: int) -> Optional[dnfile.base.MDTableRow]:
assert pe.net is not None
assert pe.net.mdtables is not None
if row_index - 1 <= 0:
return None
table: Optional[dnfile.base.ClrMetaDataTable] = pe.net.mdtables.tables.get(table_index)
if table is None:
return None
try:
return table[row_index - 1]
except IndexError:
return None
def resolve_nested_typedef_name(
nested_class_table: dict, index: int, typedef: dnfile.mdtable.TypeDefRow, pe: dnfile.dnPE
) -> tuple[str, tuple[str, ...]]:
"""Resolves all nested TypeDef class names. Returns the namespace as a str and the nested TypeRef name as a tuple"""
if index in nested_class_table:
typedef_name = []
name = str(typedef.TypeName)
# Append the current typedef name
typedef_name.append(name)
while nested_class_table[index] in nested_class_table:
# Iterate through the typedef table to resolve the nested name
table_row = get_dotnet_table_row(pe, dnfile.mdtable.TypeDef.number, nested_class_table[index])
if table_row is None:
return str(typedef.TypeNamespace), tuple(typedef_name[::-1])
name = str(table_row.TypeName)
typedef_name.append(name)
index = nested_class_table[index]
# Document the root enclosing details
table_row = get_dotnet_table_row(pe, dnfile.mdtable.TypeDef.number, nested_class_table[index])
if table_row is None:
return str(typedef.TypeNamespace), tuple(typedef_name[::-1])
enclosing_name = str(table_row.TypeName)
typedef_name.append(enclosing_name)
return str(table_row.TypeNamespace), tuple(typedef_name[::-1])
else:
return str(typedef.TypeNamespace), (str(typedef.TypeName),)
def resolve_nested_typeref_name(
index: int, typeref: dnfile.mdtable.TypeRefRow, pe: dnfile.dnPE
) -> tuple[str, tuple[str, ...]]:
"""Resolves all nested TypeRef class names. Returns the namespace as a str and the nested TypeRef name as a tuple"""
# If the ResolutionScope decodes to a typeRef type then it is nested
if isinstance(typeref.ResolutionScope.table, dnfile.mdtable.TypeRef):
typeref_name = []
name = str(typeref.TypeName)
# Not appending the current typeref name to avoid potential duplicate
# Validate index
table_row = get_dotnet_table_row(pe, dnfile.mdtable.TypeRef.number, index)
if table_row is None:
return str(typeref.TypeNamespace), (str(typeref.TypeName),)
while isinstance(table_row.ResolutionScope.table, dnfile.mdtable.TypeRef):
# Iterate through the typeref table to resolve the nested name
typeref_name.append(name)
name = str(table_row.TypeName)
table_row = get_dotnet_table_row(pe, dnfile.mdtable.TypeRef.number, table_row.ResolutionScope.row_index)
if table_row is None:
return str(typeref.TypeNamespace), tuple(typeref_name[::-1])
# Document the root enclosing details
typeref_name.append(str(table_row.TypeName))
return str(table_row.TypeNamespace), tuple(typeref_name[::-1])
else:
return str(typeref.TypeNamespace), (str(typeref.TypeName),)
def get_dotnet_nested_class_table_index(pe: dnfile.dnPE) -> dict[int, int]:
"""Build index for EnclosingClass based off the NestedClass row index in the nestedclass table"""
nested_class_table = {}
# Used to find nested classes in typedef
for _, nestedclass in iter_dotnet_table(pe, dnfile.mdtable.NestedClass.number):
assert isinstance(nestedclass, dnfile.mdtable.NestedClassRow)
nested_class_table[nestedclass.NestedClass.row_index] = nestedclass.EnclosingClass.row_index
return nested_class_table
def get_dotnet_types(pe: dnfile.dnPE) -> Iterator[DnType]:
"""get .NET types from TypeDef and TypeRef tables"""
nested_class_table = get_dotnet_nested_class_table_index(pe)
for rid, typedef in iter_dotnet_table(pe, dnfile.mdtable.TypeDef.number):
assert isinstance(typedef, dnfile.mdtable.TypeDefRow)
typedefnamespace, typedefname = resolve_nested_typedef_name(nested_class_table, rid, typedef, pe)
typedef_token: int = calculate_dotnet_token_value(dnfile.mdtable.TypeDef.number, rid)
yield DnType(typedef_token, typedef.TypeName, namespace=typedef.TypeNamespace)
yield DnType(typedef_token, typedefname, namespace=typedefnamespace)
for rid, typeref in iter_dotnet_table(pe, dnfile.mdtable.TypeRef.number):
assert isinstance(typeref, dnfile.mdtable.TypeRefRow)
typerefnamespace, typerefname = resolve_nested_typeref_name(typeref.ResolutionScope.row_index, typeref, pe)
typeref_token: int = calculate_dotnet_token_value(dnfile.mdtable.TypeRef.number, rid)
yield DnType(typeref_token, typeref.TypeName, namespace=typeref.TypeNamespace)
yield DnType(typeref_token, typerefname, namespace=typerefnamespace)
def calculate_dotnet_token_value(table: int, rid: int) -> int:
@@ -326,7 +442,7 @@ def is_dotnet_mixed_mode(pe: dnfile.dnPE) -> bool:
return not bool(pe.net.Flags.CLR_ILONLY)
def iter_dotnet_table(pe: dnfile.dnPE, table_index: int) -> Iterator[Tuple[int, dnfile.base.MDTableRow]]:
def iter_dotnet_table(pe: dnfile.dnPE, table_index: int) -> Iterator[tuple[int, dnfile.base.MDTableRow]]:
assert pe.net is not None
assert pe.net.mdtables is not None

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2023 Mandiant, Inc. All Rights Reserved.
# Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
@@ -9,7 +9,7 @@
from __future__ import annotations
import logging
from typing import TYPE_CHECKING, Tuple, Union, Iterator, Optional
from typing import TYPE_CHECKING, Union, Iterator, Optional
if TYPE_CHECKING:
from capa.features.extractors.dnfile.extractor import DnFileFeatureExtractorCache
@@ -61,7 +61,7 @@ def get_callee(
return callee
def extract_insn_api_features(fh: FunctionHandle, bh, ih: InsnHandle) -> Iterator[Tuple[Feature, Address]]:
def extract_insn_api_features(fh: FunctionHandle, bh, ih: InsnHandle) -> Iterator[tuple[Feature, Address]]:
"""parse instruction API features"""
if ih.inner.opcode not in (
OpCodes.Call,
@@ -83,7 +83,7 @@ def extract_insn_api_features(fh: FunctionHandle, bh, ih: InsnHandle) -> Iterato
yield API(name), ih.address
def extract_insn_property_features(fh: FunctionHandle, bh, ih: InsnHandle) -> Iterator[Tuple[Feature, Address]]:
def extract_insn_property_features(fh: FunctionHandle, bh, ih: InsnHandle) -> Iterator[tuple[Feature, Address]]:
"""parse instruction property features"""
name: Optional[str] = None
access: Optional[str] = None
@@ -118,7 +118,7 @@ def extract_insn_property_features(fh: FunctionHandle, bh, ih: InsnHandle) -> It
def extract_insn_namespace_class_features(
fh: FunctionHandle, bh, ih: InsnHandle
) -> Iterator[Tuple[Union[Namespace, Class], Address]]:
) -> Iterator[tuple[Union[Namespace, Class], Address]]:
"""parse instruction namespace and class features"""
type_: Optional[Union[DnType, DnUnmanagedMethod]] = None
@@ -173,13 +173,13 @@ def extract_insn_namespace_class_features(
yield Namespace(type_.namespace), ih.address
def extract_insn_number_features(fh, bh, ih: InsnHandle) -> Iterator[Tuple[Feature, Address]]:
def extract_insn_number_features(fh, bh, ih: InsnHandle) -> Iterator[tuple[Feature, Address]]:
"""parse instruction number features"""
if ih.inner.is_ldc():
yield Number(ih.inner.get_ldc()), ih.address
def extract_insn_string_features(fh: FunctionHandle, bh, ih: InsnHandle) -> Iterator[Tuple[Feature, Address]]:
def extract_insn_string_features(fh: FunctionHandle, bh, ih: InsnHandle) -> Iterator[tuple[Feature, Address]]:
"""parse instruction string features"""
if not ih.inner.is_ldstr():
return
@@ -197,7 +197,7 @@ def extract_insn_string_features(fh: FunctionHandle, bh, ih: InsnHandle) -> Iter
def extract_unmanaged_call_characteristic_features(
fh: FunctionHandle, bb: BBHandle, ih: InsnHandle
) -> Iterator[Tuple[Characteristic, Address]]:
) -> Iterator[tuple[Characteristic, Address]]:
if ih.inner.opcode not in (OpCodes.Call, OpCodes.Callvirt, OpCodes.Jmp):
return
@@ -209,7 +209,7 @@ def extract_unmanaged_call_characteristic_features(
yield Characteristic("unmanaged call"), ih.address
def extract_features(fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle) -> Iterator[Tuple[Feature, Address]]:
def extract_features(fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle) -> Iterator[tuple[Feature, Address]]:
"""extract instruction features"""
for inst_handler in INSTRUCTION_HANDLERS:
for feature, addr in inst_handler(fh, bbh, ih):

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2023 Mandiant, Inc. All Rights Reserved.
# Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
@@ -10,11 +10,13 @@ from typing import Optional
class DnType:
def __init__(self, token: int, class_: str, namespace: str = "", member: str = "", access: Optional[str] = None):
def __init__(
self, token: int, class_: tuple[str, ...], namespace: str = "", member: str = "", access: Optional[str] = None
):
self.token: int = token
self.access: Optional[str] = access
self.namespace: str = namespace
self.class_: str = class_
self.class_: tuple[str, ...] = class_
if member == ".ctor":
member = "ctor"
@@ -42,9 +44,13 @@ class DnType:
return str(self)
@staticmethod
def format_name(class_: str, namespace: str = "", member: str = ""):
def format_name(class_: tuple[str, ...], namespace: str = "", member: str = ""):
if len(class_) > 1:
class_str = "/".join(class_) # Concat items in tuple, separated by a "/"
else:
class_str = "".join(class_) # Convert tuple to str
# like File::OpenRead
name: str = f"{class_}::{member}" if member else class_
name: str = f"{class_str}::{member}" if member else class_str
if namespace:
# like System.IO.File::OpenRead
name = f"{namespace}.{name}"

View File

@@ -1,158 +0,0 @@
# Copyright (C) 2023 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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.
import logging
from typing import Tuple, Iterator
from pathlib import Path
import dnfile
import pefile
from capa.features.common import (
OS,
OS_ANY,
ARCH_ANY,
ARCH_I386,
FORMAT_PE,
ARCH_AMD64,
FORMAT_DOTNET,
Arch,
Format,
Feature,
)
from capa.features.address import NO_ADDRESS, Address, AbsoluteVirtualAddress
from capa.features.extractors.base_extractor import FeatureExtractor
logger = logging.getLogger(__name__)
def extract_file_format(**kwargs) -> Iterator[Tuple[Feature, Address]]:
yield Format(FORMAT_PE), NO_ADDRESS
yield Format(FORMAT_DOTNET), NO_ADDRESS
def extract_file_os(**kwargs) -> Iterator[Tuple[Feature, Address]]:
yield OS(OS_ANY), NO_ADDRESS
def extract_file_arch(pe: dnfile.dnPE, **kwargs) -> Iterator[Tuple[Feature, Address]]:
# to distinguish in more detail, see https://stackoverflow.com/a/23614024/10548020
# .NET 4.5 added option: any CPU, 32-bit preferred
assert pe.net is not None
assert pe.net.Flags is not None
if pe.net.Flags.CLR_32BITREQUIRED and pe.PE_TYPE == pefile.OPTIONAL_HEADER_MAGIC_PE:
yield Arch(ARCH_I386), NO_ADDRESS
elif not pe.net.Flags.CLR_32BITREQUIRED and pe.PE_TYPE == pefile.OPTIONAL_HEADER_MAGIC_PE_PLUS:
yield Arch(ARCH_AMD64), NO_ADDRESS
else:
yield Arch(ARCH_ANY), NO_ADDRESS
def extract_file_features(pe: dnfile.dnPE) -> Iterator[Tuple[Feature, Address]]:
for file_handler in FILE_HANDLERS:
for feature, address in file_handler(pe=pe): # type: ignore
yield feature, address
FILE_HANDLERS = (
# extract_file_export_names,
# extract_file_import_names,
# extract_file_section_names,
# extract_file_strings,
# extract_file_function_names,
extract_file_format,
)
def extract_global_features(pe: dnfile.dnPE) -> Iterator[Tuple[Feature, Address]]:
for handler in GLOBAL_HANDLERS:
for feature, addr in handler(pe=pe): # type: ignore
yield feature, addr
GLOBAL_HANDLERS = (
extract_file_os,
extract_file_arch,
)
class DnfileFeatureExtractor(FeatureExtractor):
def __init__(self, path: Path):
super().__init__()
self.path: Path = path
self.pe: dnfile.dnPE = dnfile.dnPE(str(path))
def get_base_address(self) -> AbsoluteVirtualAddress:
return AbsoluteVirtualAddress(0x0)
def get_entry_point(self) -> int:
# self.pe.net.Flags.CLT_NATIVE_ENTRYPOINT
# True: native EP: Token
# False: managed EP: RVA
assert self.pe.net is not None
assert self.pe.net.struct is not None
return self.pe.net.struct.EntryPointTokenOrRva
def extract_global_features(self):
yield from extract_global_features(self.pe)
def extract_file_features(self):
yield from extract_file_features(self.pe)
def is_dotnet_file(self) -> bool:
return bool(self.pe.net)
def is_mixed_mode(self) -> bool:
assert self.pe is not None
assert self.pe.net is not None
assert self.pe.net.Flags is not None
return not bool(self.pe.net.Flags.CLR_ILONLY)
def get_runtime_version(self) -> Tuple[int, int]:
assert self.pe is not None
assert self.pe.net is not None
assert self.pe.net.struct is not None
return self.pe.net.struct.MajorRuntimeVersion, self.pe.net.struct.MinorRuntimeVersion
def get_meta_version_string(self) -> str:
assert self.pe.net is not None
assert self.pe.net.metadata is not None
assert self.pe.net.metadata.struct is not None
assert self.pe.net.metadata.struct.Version is not None
vbuf = self.pe.net.metadata.struct.Version
assert isinstance(vbuf, bytes)
return vbuf.rstrip(b"\x00").decode("utf-8")
def get_functions(self):
raise NotImplementedError("DnfileFeatureExtractor can only be used to extract file features")
def extract_function_features(self, f):
raise NotImplementedError("DnfileFeatureExtractor can only be used to extract file features")
def get_basic_blocks(self, f):
raise NotImplementedError("DnfileFeatureExtractor can only be used to extract file features")
def extract_basic_block_features(self, f, bb):
raise NotImplementedError("DnfileFeatureExtractor can only be used to extract file features")
def get_instructions(self, f, bb):
raise NotImplementedError("DnfileFeatureExtractor can only be used to extract file features")
def extract_insn_features(self, f, bb, insn):
raise NotImplementedError("DnfileFeatureExtractor can only be used to extract file features")
def is_library_function(self, va):
raise NotImplementedError("DnfileFeatureExtractor can only be used to extract file features")
def get_function_name(self, va):
raise NotImplementedError("DnfileFeatureExtractor can only be used to extract file features")

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2023 Mandiant, Inc. All Rights Reserved.
# Copyright (C) 2022 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
@@ -6,7 +6,7 @@
# 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.
import logging
from typing import Tuple, Iterator
from typing import Iterator
from pathlib import Path
import dnfile
@@ -31,42 +31,45 @@ from capa.features.common import (
Characteristic,
)
from capa.features.address import NO_ADDRESS, Address, DNTokenAddress
from capa.features.extractors.base_extractor import FeatureExtractor
from capa.features.extractors.dnfile.types import DnType
from capa.features.extractors.base_extractor import SampleHashes, StaticFeatureExtractor
from capa.features.extractors.dnfile.helpers import (
DnType,
iter_dotnet_table,
is_dotnet_mixed_mode,
get_dotnet_managed_imports,
get_dotnet_managed_methods,
resolve_nested_typedef_name,
resolve_nested_typeref_name,
calculate_dotnet_token_value,
get_dotnet_unmanaged_imports,
get_dotnet_nested_class_table_index,
)
logger = logging.getLogger(__name__)
def extract_file_format(**kwargs) -> Iterator[Tuple[Format, Address]]:
yield Format(FORMAT_PE), NO_ADDRESS
def extract_file_format(**kwargs) -> Iterator[tuple[Format, Address]]:
yield Format(FORMAT_DOTNET), NO_ADDRESS
yield Format(FORMAT_PE), NO_ADDRESS
def extract_file_import_names(pe: dnfile.dnPE, **kwargs) -> Iterator[Tuple[Import, Address]]:
def extract_file_import_names(pe: dnfile.dnPE, **kwargs) -> Iterator[tuple[Import, Address]]:
for method in get_dotnet_managed_imports(pe):
# like System.IO.File::OpenRead
yield Import(str(method)), DNTokenAddress(method.token)
for imp in get_dotnet_unmanaged_imports(pe):
# like kernel32.CreateFileA
for name in capa.features.extractors.helpers.generate_symbols(imp.module, imp.method):
for name in capa.features.extractors.helpers.generate_symbols(imp.module, imp.method, include_dll=True):
yield Import(name), DNTokenAddress(imp.token)
def extract_file_function_names(pe: dnfile.dnPE, **kwargs) -> Iterator[Tuple[FunctionName, Address]]:
def extract_file_function_names(pe: dnfile.dnPE, **kwargs) -> Iterator[tuple[FunctionName, Address]]:
for method in get_dotnet_managed_methods(pe):
yield FunctionName(str(method)), DNTokenAddress(method.token)
def extract_file_namespace_features(pe: dnfile.dnPE, **kwargs) -> Iterator[Tuple[Namespace, Address]]:
def extract_file_namespace_features(pe: dnfile.dnPE, **kwargs) -> Iterator[tuple[Namespace, Address]]:
"""emit namespace features from TypeRef and TypeDef tables"""
# namespaces may be referenced multiple times, so we need to filter
@@ -75,12 +78,12 @@ def extract_file_namespace_features(pe: dnfile.dnPE, **kwargs) -> Iterator[Tuple
for _, typedef in iter_dotnet_table(pe, dnfile.mdtable.TypeDef.number):
# emit internal .NET namespaces
assert isinstance(typedef, dnfile.mdtable.TypeDefRow)
namespaces.add(typedef.TypeNamespace)
namespaces.add(str(typedef.TypeNamespace))
for _, typeref in iter_dotnet_table(pe, dnfile.mdtable.TypeRef.number):
# emit external .NET namespaces
assert isinstance(typeref, dnfile.mdtable.TypeRefRow)
namespaces.add(typeref.TypeNamespace)
namespaces.add(str(typeref.TypeNamespace))
# namespaces may be empty, discard
namespaces.discard("")
@@ -90,28 +93,34 @@ def extract_file_namespace_features(pe: dnfile.dnPE, **kwargs) -> Iterator[Tuple
yield Namespace(namespace), NO_ADDRESS
def extract_file_class_features(pe: dnfile.dnPE, **kwargs) -> Iterator[Tuple[Class, Address]]:
def extract_file_class_features(pe: dnfile.dnPE, **kwargs) -> Iterator[tuple[Class, Address]]:
"""emit class features from TypeRef and TypeDef tables"""
nested_class_table = get_dotnet_nested_class_table_index(pe)
for rid, typedef in iter_dotnet_table(pe, dnfile.mdtable.TypeDef.number):
# emit internal .NET classes
assert isinstance(typedef, dnfile.mdtable.TypeDefRow)
typedefnamespace, typedefname = resolve_nested_typedef_name(nested_class_table, rid, typedef, pe)
token = calculate_dotnet_token_value(dnfile.mdtable.TypeDef.number, rid)
yield Class(DnType.format_name(typedef.TypeName, namespace=typedef.TypeNamespace)), DNTokenAddress(token)
yield Class(DnType.format_name(typedefname, namespace=typedefnamespace)), DNTokenAddress(token)
for rid, typeref in iter_dotnet_table(pe, dnfile.mdtable.TypeRef.number):
# emit external .NET classes
assert isinstance(typeref, dnfile.mdtable.TypeRefRow)
typerefnamespace, typerefname = resolve_nested_typeref_name(typeref.ResolutionScope.row_index, typeref, pe)
token = calculate_dotnet_token_value(dnfile.mdtable.TypeRef.number, rid)
yield Class(DnType.format_name(typeref.TypeName, namespace=typeref.TypeNamespace)), DNTokenAddress(token)
yield Class(DnType.format_name(typerefname, namespace=typerefnamespace)), DNTokenAddress(token)
def extract_file_os(**kwargs) -> Iterator[Tuple[OS, Address]]:
def extract_file_os(**kwargs) -> Iterator[tuple[OS, Address]]:
yield OS(OS_ANY), NO_ADDRESS
def extract_file_arch(pe: dnfile.dnPE, **kwargs) -> Iterator[Tuple[Arch, Address]]:
def extract_file_arch(pe: dnfile.dnPE, **kwargs) -> Iterator[tuple[Arch, Address]]:
# to distinguish in more detail, see https://stackoverflow.com/a/23614024/10548020
# .NET 4.5 added option: any CPU, 32-bit preferred
assert pe.net is not None
@@ -125,18 +134,18 @@ def extract_file_arch(pe: dnfile.dnPE, **kwargs) -> Iterator[Tuple[Arch, Address
yield Arch(ARCH_ANY), NO_ADDRESS
def extract_file_strings(pe: dnfile.dnPE, **kwargs) -> Iterator[Tuple[String, Address]]:
def extract_file_strings(pe: dnfile.dnPE, **kwargs) -> Iterator[tuple[String, Address]]:
yield from capa.features.extractors.common.extract_file_strings(pe.__data__)
def extract_file_mixed_mode_characteristic_features(
pe: dnfile.dnPE, **kwargs
) -> Iterator[Tuple[Characteristic, Address]]:
) -> Iterator[tuple[Characteristic, Address]]:
if is_dotnet_mixed_mode(pe):
yield Characteristic("mixed mode"), NO_ADDRESS
def extract_file_features(pe: dnfile.dnPE) -> Iterator[Tuple[Feature, Address]]:
def extract_file_features(pe: dnfile.dnPE) -> Iterator[tuple[Feature, Address]]:
for file_handler in FILE_HANDLERS:
for feature, addr in file_handler(pe=pe): # type: ignore
yield feature, addr
@@ -153,7 +162,7 @@ FILE_HANDLERS = (
)
def extract_global_features(pe: dnfile.dnPE) -> Iterator[Tuple[Feature, Address]]:
def extract_global_features(pe: dnfile.dnPE) -> Iterator[tuple[Feature, Address]]:
for handler in GLOBAL_HANDLERS:
for feature, va in handler(pe=pe): # type: ignore
yield feature, va
@@ -165,9 +174,9 @@ GLOBAL_HANDLERS = (
)
class DotnetFileFeatureExtractor(FeatureExtractor):
class DotnetFileFeatureExtractor(StaticFeatureExtractor):
def __init__(self, path: Path):
super().__init__()
super().__init__(hashes=SampleHashes.from_bytes(path.read_bytes()))
self.path: Path = path
self.pe: dnfile.dnPE = dnfile.dnPE(str(path))
@@ -195,7 +204,7 @@ class DotnetFileFeatureExtractor(FeatureExtractor):
def is_mixed_mode(self) -> bool:
return is_dotnet_mixed_mode(self.pe)
def get_runtime_version(self) -> Tuple[int, int]:
def get_runtime_version(self) -> tuple[int, int]:
assert self.pe.net is not None
assert self.pe.net.struct is not None
assert self.pe.net.struct.MajorRuntimeVersion is not None

View File

@@ -0,0 +1,58 @@
# Copyright (C) 2024 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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.
import logging
from typing import Iterator
import capa.features.extractors.helpers
from capa.features.insn import API, Number
from capa.features.common import String, Feature
from capa.features.address import Address
from capa.features.extractors.base_extractor import CallHandle, ThreadHandle, ProcessHandle
from capa.features.extractors.drakvuf.models import Call
logger = logging.getLogger(__name__)
def extract_call_features(ph: ProcessHandle, th: ThreadHandle, ch: CallHandle) -> Iterator[tuple[Feature, Address]]:
"""
This method extracts the given call's features (such as API name and arguments),
and returns them as API, Number, and String features.
args:
ph: process handle (for defining the extraction scope)
th: thread handle (for defining the extraction scope)
ch: call handle (for defining the extraction scope)
yields:
Feature, address; where Feature is either: API, Number, or String.
"""
call: Call = ch.inner
# list similar to disassembly: arguments right-to-left, call
for arg_value in reversed(call.arguments.values()):
try:
yield Number(int(arg_value, 0)), ch.address
except ValueError:
# DRAKVUF automatically resolves the contents of memory addresses, (e.g. Arg1="0xc6f217efe0:\"ntdll.dll\"").
# For those cases we yield the entire string as it, since yielding the address only would
# likely not provide any matches, and yielding just the memory contentswould probably be misleading,
# but yielding the entire string would be helpful for an analyst looking at the verbose output
yield String(arg_value), ch.address
for name in capa.features.extractors.helpers.generate_symbols("", call.name):
yield API(name), ch.address
def extract_features(ph: ProcessHandle, th: ThreadHandle, ch: CallHandle) -> Iterator[tuple[Feature, Address]]:
for handler in CALL_HANDLERS:
for feature, addr in handler(ph, th, ch):
yield feature, addr
CALL_HANDLERS = (extract_call_features,)

View File

@@ -0,0 +1,96 @@
# Copyright (C) 2024 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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.
import logging
from typing import Union, Iterator
import capa.features.extractors.drakvuf.call
import capa.features.extractors.drakvuf.file
import capa.features.extractors.drakvuf.thread
import capa.features.extractors.drakvuf.global_
import capa.features.extractors.drakvuf.process
from capa.features.common import Feature, Characteristic
from capa.features.address import NO_ADDRESS, Address, ThreadAddress, ProcessAddress, AbsoluteVirtualAddress, _NoAddress
from capa.features.extractors.base_extractor import (
CallHandle,
SampleHashes,
ThreadHandle,
ProcessHandle,
DynamicFeatureExtractor,
)
from capa.features.extractors.drakvuf.models import Call, DrakvufReport
from capa.features.extractors.drakvuf.helpers import index_calls
logger = logging.getLogger(__name__)
class DrakvufExtractor(DynamicFeatureExtractor):
def __init__(self, report: DrakvufReport):
super().__init__(
# DRAKVUF currently does not yield hash information about the sample in its output
hashes=SampleHashes(md5="", sha1="", sha256="")
)
self.report: DrakvufReport = report
# sort the api calls to prevent going through the entire list each time
self.sorted_calls: dict[ProcessAddress, dict[ThreadAddress, list[Call]]] = index_calls(report)
# pre-compute these because we'll yield them at *every* scope.
self.global_features = list(capa.features.extractors.drakvuf.global_.extract_features(self.report))
def get_base_address(self) -> Union[AbsoluteVirtualAddress, _NoAddress, None]:
# DRAKVUF currently does not yield information about the PE's address
return NO_ADDRESS
def extract_global_features(self) -> Iterator[tuple[Feature, Address]]:
yield from self.global_features
def extract_file_features(self) -> Iterator[tuple[Feature, Address]]:
yield from capa.features.extractors.drakvuf.file.extract_features(self.report)
def get_processes(self) -> Iterator[ProcessHandle]:
yield from capa.features.extractors.drakvuf.file.get_processes(self.sorted_calls)
def extract_process_features(self, ph: ProcessHandle) -> Iterator[tuple[Feature, Address]]:
yield from capa.features.extractors.drakvuf.process.extract_features(ph)
def get_process_name(self, ph: ProcessHandle) -> str:
return ph.inner["process_name"]
def get_threads(self, ph: ProcessHandle) -> Iterator[ThreadHandle]:
yield from capa.features.extractors.drakvuf.process.get_threads(self.sorted_calls, ph)
def extract_thread_features(self, ph: ProcessHandle, th: ThreadHandle) -> Iterator[tuple[Feature, Address]]:
if False:
# force this routine to be a generator,
# but we don't actually have any elements to generate.
yield Characteristic("never"), NO_ADDRESS
return
def get_calls(self, ph: ProcessHandle, th: ThreadHandle) -> Iterator[CallHandle]:
yield from capa.features.extractors.drakvuf.thread.get_calls(self.sorted_calls, ph, th)
def get_call_name(self, ph: ProcessHandle, th: ThreadHandle, ch: CallHandle) -> str:
call: Call = ch.inner
call_name = "{}({}){}".format(
call.name,
", ".join(f"{arg_name}={arg_value}" for arg_name, arg_value in call.arguments.items()),
(f" -> {getattr(call, 'return_value', '')}"), # SysCalls don't have a return value, while WinApi calls do
)
return call_name
def extract_call_features(
self, ph: ProcessHandle, th: ThreadHandle, ch: CallHandle
) -> Iterator[tuple[Feature, Address]]:
yield from capa.features.extractors.drakvuf.call.extract_features(ph, th, ch)
@classmethod
def from_report(cls, report: Iterator[dict]) -> "DrakvufExtractor":
dr = DrakvufReport.from_raw_report(report)
return DrakvufExtractor(report=dr)

View File

@@ -0,0 +1,56 @@
# Copyright (C) 2024 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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.
import logging
from typing import Iterator
from capa.features.file import Import
from capa.features.common import Feature
from capa.features.address import Address, ThreadAddress, ProcessAddress, AbsoluteVirtualAddress
from capa.features.extractors.helpers import generate_symbols
from capa.features.extractors.base_extractor import ProcessHandle
from capa.features.extractors.drakvuf.models import Call, DrakvufReport
logger = logging.getLogger(__name__)
def get_processes(calls: dict[ProcessAddress, dict[ThreadAddress, list[Call]]]) -> Iterator[ProcessHandle]:
"""
Get all the created processes for a sample.
"""
for proc_addr, calls_per_thread in calls.items():
sample_call = next(iter(calls_per_thread.values()))[0] # get process name
yield ProcessHandle(proc_addr, inner={"process_name": sample_call.process_name})
def extract_import_names(report: DrakvufReport) -> Iterator[tuple[Feature, Address]]:
"""
Extract imported function names.
"""
if report.loaded_dlls is None:
return
dlls = report.loaded_dlls
for dll in dlls:
dll_base_name = dll.name.split("\\")[-1]
for function_name, function_address in dll.imports.items():
for name in generate_symbols(dll_base_name, function_name, include_dll=True):
yield Import(name), AbsoluteVirtualAddress(function_address)
def extract_features(report: DrakvufReport) -> Iterator[tuple[Feature, Address]]:
for handler in FILE_HANDLERS:
for feature, addr in handler(report):
yield feature, addr
FILE_HANDLERS = (
# TODO(yelhamer): extract more file features from other DRAKVUF plugins
# https://github.com/mandiant/capa/issues/2169
extract_import_names,
)

View File

@@ -0,0 +1,44 @@
# Copyright (C) 2024 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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.
import logging
from typing import Iterator
from capa.features.common import OS, FORMAT_PE, ARCH_AMD64, OS_WINDOWS, Arch, Format, Feature
from capa.features.address import NO_ADDRESS, Address
from capa.features.extractors.drakvuf.models import DrakvufReport
logger = logging.getLogger(__name__)
def extract_format(report: DrakvufReport) -> Iterator[tuple[Feature, Address]]:
# DRAKVUF sandbox currently supports only Windows as the guest: https://drakvuf-sandbox.readthedocs.io/en/latest/usage/getting_started.html
yield Format(FORMAT_PE), NO_ADDRESS
def extract_os(report: DrakvufReport) -> Iterator[tuple[Feature, Address]]:
# DRAKVUF sandbox currently supports only PE files: https://drakvuf-sandbox.readthedocs.io/en/latest/usage/getting_started.html
yield OS(OS_WINDOWS), NO_ADDRESS
def extract_arch(report: DrakvufReport) -> Iterator[tuple[Feature, Address]]:
# DRAKVUF sandbox currently supports only x64 Windows as the guest: https://drakvuf-sandbox.readthedocs.io/en/latest/usage/getting_started.html
yield Arch(ARCH_AMD64), NO_ADDRESS
def extract_features(report: DrakvufReport) -> Iterator[tuple[Feature, Address]]:
for global_handler in GLOBAL_HANDLER:
for feature, addr in global_handler(report):
yield feature, addr
GLOBAL_HANDLER = (
extract_format,
extract_os,
extract_arch,
)

View File

@@ -0,0 +1,38 @@
# Copyright (C) 2024 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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.
import itertools
from capa.features.address import ThreadAddress, ProcessAddress
from capa.features.extractors.drakvuf.models import Call, DrakvufReport
def index_calls(report: DrakvufReport) -> dict[ProcessAddress, dict[ThreadAddress, list[Call]]]:
# this method organizes calls into processes and threads, and then sorts them based on
# timestamp so that we can address individual calls per index (CallAddress requires call index)
result: dict[ProcessAddress, dict[ThreadAddress, list[Call]]] = {}
for call in itertools.chain(report.syscalls, report.apicalls):
if call.pid == 0:
# DRAKVUF captures api/native calls from all processes running on the system.
# we ignore the pid 0 since it's a system process and it's unlikely for it to
# be hijacked or so on, in addition to capa addresses not supporting null pids
continue
proc_addr = ProcessAddress(pid=call.pid, ppid=call.ppid)
thread_addr = ThreadAddress(process=proc_addr, tid=call.tid)
if proc_addr not in result:
result[proc_addr] = {}
if thread_addr not in result[proc_addr]:
result[proc_addr][thread_addr] = []
result[proc_addr][thread_addr].append(call)
for proc, threads in result.items():
for thread in threads:
result[proc][thread].sort(key=lambda call: call.timestamp)
return result

View File

@@ -0,0 +1,137 @@
# Copyright (C) 2024 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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.
import logging
from typing import Any, Iterator
from pydantic import Field, BaseModel, ConfigDict, model_validator
logger = logging.getLogger(__name__)
REQUIRED_SYSCALL_FIELD_NAMES = {
"Plugin",
"TimeStamp",
"PID",
"PPID",
"TID",
"UserName",
"UserId",
"ProcessName",
"Method",
"EventUID",
"Module",
"vCPU",
"CR3",
"Syscall",
"NArgs",
}
class ConciseModel(BaseModel):
ConfigDict(extra="ignore")
class DiscoveredDLL(ConciseModel):
plugin_name: str = Field(alias="Plugin")
event: str = Field(alias="Event")
name: str = Field(alias="DllName")
pid: int = Field(alias="PID")
class LoadedDLL(ConciseModel):
plugin_name: str = Field(alias="Plugin")
event: str = Field(alias="Event")
name: str = Field(alias="DllName")
imports: dict[str, int] = Field(alias="Rva")
class Call(ConciseModel):
plugin_name: str = Field(alias="Plugin")
timestamp: str = Field(alias="TimeStamp")
process_name: str = Field(alias="ProcessName")
ppid: int = Field(alias="PPID")
pid: int = Field(alias="PID")
tid: int = Field(alias="TID")
name: str = Field(alias="Method")
arguments: dict[str, str]
class WinApiCall(Call):
# This class models Windows API calls captured by DRAKVUF (DLLs, etc.).
arguments: dict[str, str] = Field(alias="Arguments")
event: str = Field(alias="Event")
return_value: str = Field(alias="ReturnValue")
@model_validator(mode="before")
@classmethod
def build_arguments(cls, values: dict[str, Any]) -> dict[str, Any]:
args = values["Arguments"]
values["Arguments"] = dict(arg.split("=", 1) for arg in args)
return values
class SystemCall(Call):
# This class models native Windows API calls captured by DRAKVUF.
# Schema: {
# "Plugin": "syscall",
# "TimeStamp": "1716999134.582553",
# "PID": 3888, "PPID": 2852, "TID": 368, "UserName": "SessionID", "UserId": 2,
# "ProcessName": "\\Device\\HarddiskVolume2\\Windows\\explorer.exe",
# "Method": "NtSetIoCompletionEx",
# "EventUID": "0x27",
# "Module": "nt",
# "vCPU": 0,
# "CR3": "0x119b1002",
# "Syscall": 419,
# "NArgs": 6,
# "IoCompletionHandle": "0xffffffff80001ac0", "IoCompletionReserveHandle": "0xffffffff8000188c",
# "KeyContext": "0x0", "ApcContext": "0x2", "IoStatus": "0x7ffb00000000", "IoStatusInformation": "0x0"
# }
# The keys up until "NArgs" are common to all the native calls that DRAKVUF reports, with
# the remaining keys representing the call's specific arguments.
syscall_number: int = Field(alias="Syscall")
module: str = Field(alias="Module")
nargs: int = Field(alias="NArgs")
@model_validator(mode="before")
@classmethod
def build_extra(cls, values: dict[str, Any]) -> dict[str, Any]:
# DRAKVUF stores argument names and values as entries in the syscall's entry.
# This model validator collects those arguments into a list in the model.
values["arguments"] = {
name: value for name, value in values.items() if name not in REQUIRED_SYSCALL_FIELD_NAMES
}
return values
class DrakvufReport(ConciseModel):
syscalls: list[SystemCall] = []
apicalls: list[WinApiCall] = []
discovered_dlls: list[DiscoveredDLL] = []
loaded_dlls: list[LoadedDLL] = []
@classmethod
def from_raw_report(cls, entries: Iterator[dict]) -> "DrakvufReport":
report = cls()
for entry in entries:
plugin = entry.get("Plugin")
# TODO(yelhamer): add support for more DRAKVUF plugins
# https://github.com/mandiant/capa/issues/2181
if plugin == "syscall":
report.syscalls.append(SystemCall(**entry))
elif plugin == "apimon":
event = entry.get("Event")
if event == "api_called":
report.apicalls.append(WinApiCall(**entry))
elif event == "dll_loaded":
report.loaded_dlls.append(LoadedDLL(**entry))
elif event == "dll_discovered":
report.discovered_dlls.append(DiscoveredDLL(**entry))
return report

View File

@@ -0,0 +1,40 @@
# Copyright (C) 2024 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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.
import logging
from typing import Iterator
from capa.features.common import String, Feature
from capa.features.address import Address, ThreadAddress, ProcessAddress
from capa.features.extractors.base_extractor import ThreadHandle, ProcessHandle
from capa.features.extractors.drakvuf.models import Call
logger = logging.getLogger(__name__)
def get_threads(
calls: dict[ProcessAddress, dict[ThreadAddress, list[Call]]], ph: ProcessHandle
) -> Iterator[ThreadHandle]:
"""
Get the threads associated with a given process.
"""
for thread_addr in calls[ph.address]:
yield ThreadHandle(address=thread_addr, inner={})
def extract_process_name(ph: ProcessHandle) -> Iterator[tuple[Feature, Address]]:
yield String(ph.inner["process_name"]), ph.address
def extract_features(ph: ProcessHandle) -> Iterator[tuple[Feature, Address]]:
for handler in PROCESS_HANDLERS:
for feature, addr in handler(ph):
yield feature, addr
PROCESS_HANDLERS = (extract_process_name,)

View File

@@ -0,0 +1,24 @@
# Copyright (C) 2024 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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.
import logging
from typing import Iterator
from capa.features.address import ThreadAddress, ProcessAddress, DynamicCallAddress
from capa.features.extractors.base_extractor import CallHandle, ThreadHandle, ProcessHandle
from capa.features.extractors.drakvuf.models import Call
logger = logging.getLogger(__name__)
def get_calls(
sorted_calls: dict[ProcessAddress, dict[ThreadAddress, list[Call]]], ph: ProcessHandle, th: ThreadHandle
) -> Iterator[CallHandle]:
for i, call in enumerate(sorted_calls[ph.address][th.address]):
call_addr = DynamicCallAddress(thread=th.address, id=i)
yield CallHandle(address=call_addr, inner=call)

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2023 Mandiant, Inc. All Rights Reserved.
# Copyright (C) 2021 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
@@ -10,9 +10,12 @@ import logging
import itertools
import collections
from enum import Enum
from typing import Set, Dict, List, Tuple, BinaryIO, Iterator, Optional
from typing import TYPE_CHECKING, BinaryIO, Iterator, Optional
from dataclasses import dataclass
if TYPE_CHECKING:
import Elf # from vivisect
logger = logging.getLogger(__name__)
@@ -54,6 +57,11 @@ class OS(str, Enum):
CLOUD = "cloud"
SYLLABLE = "syllable"
NACL = "nacl"
ANDROID = "android"
DRAGONFLYBSD = "dragonfly BSD"
ILLUMOS = "illumos"
ZOS = "z/os"
UNIX = "unix"
# via readelf: https://github.com/bminor/binutils-gdb/blob/c0e94211e1ac05049a4ce7c192c9d14d1764eb3e/binutils/readelf.c#L19635-L19658
@@ -77,6 +85,8 @@ class Phdr:
paddr: int
filesz: int
buf: bytes
flags: int
memsz: int
@dataclass
@@ -105,6 +115,9 @@ class Shdr:
buf,
)
def get_name(self, elf: "ELF") -> str:
return elf.shstrtab.buf[self.name :].partition(b"\x00")[0].decode("ascii")
class ELF:
def __init__(self, f: BinaryIO):
@@ -117,6 +130,7 @@ class ELF:
self.e_phnum: int
self.e_shentsize: int
self.e_shnum: int
self.e_shstrndx: int
self.phbuf: bytes
self.shbuf: bytes
@@ -148,11 +162,15 @@ class ELF:
if self.bitness == 32:
e_phoff, e_shoff = struct.unpack_from(self.endian + "II", self.file_header, 0x1C)
self.e_phentsize, self.e_phnum = struct.unpack_from(self.endian + "HH", self.file_header, 0x2A)
self.e_shentsize, self.e_shnum = struct.unpack_from(self.endian + "HH", self.file_header, 0x2E)
self.e_shentsize, self.e_shnum, self.e_shstrndx = struct.unpack_from(
self.endian + "HHH", self.file_header, 0x2E
)
elif self.bitness == 64:
e_phoff, e_shoff = struct.unpack_from(self.endian + "QQ", self.file_header, 0x20)
self.e_phentsize, self.e_phnum = struct.unpack_from(self.endian + "HH", self.file_header, 0x36)
self.e_shentsize, self.e_shnum = struct.unpack_from(self.endian + "HH", self.file_header, 0x3A)
self.e_shentsize, self.e_shnum, self.e_shstrndx = struct.unpack_from(
self.endian + "HHH", self.file_header, 0x3A
)
else:
raise NotImplementedError()
@@ -194,7 +212,7 @@ class ELF:
15: OS.AROS,
16: OS.FENIXOS,
17: OS.CLOUD,
# 53: "SORTFIX", # i can't find any reference to this OS, i dont think it exists
# 53: "SORTFIX", # i can't find any reference to this OS, i don't think it exists
# 64: "ARM_AEABI", # not an OS
# 97: "ARM", # not an OS
# 255: "STANDALONE", # not an OS
@@ -292,6 +310,9 @@ class ELF:
98: "TPC",
99: "SNP1K",
100: "ST200",
# https://www.sco.com/developers/gabi/latest/ch4.eheader.html
183: "aarch64",
243: "riscv",
}
@property
@@ -303,24 +324,23 @@ class ELF:
phent_offset = i * self.e_phentsize
phent = self.phbuf[phent_offset : phent_offset + self.e_phentsize]
(p_type,) = struct.unpack_from(self.endian + "I", phent, 0x0)
logger.debug("ph:p_type: 0x%04x", p_type)
if self.bitness == 32:
p_offset, p_vaddr, p_paddr, p_filesz = struct.unpack_from(self.endian + "IIII", phent, 0x4)
p_type, p_offset, p_vaddr, p_paddr, p_filesz, p_memsz, p_flags = struct.unpack_from(
self.endian + "IIIIIII", phent, 0x0
)
elif self.bitness == 64:
p_offset, p_vaddr, p_paddr, p_filesz = struct.unpack_from(self.endian + "QQQQ", phent, 0x8)
p_type, p_flags, p_offset, p_vaddr, p_paddr, p_filesz, p_memsz = struct.unpack_from(
self.endian + "IIQQQQQ", phent, 0x0
)
else:
raise NotImplementedError()
logger.debug("ph:p_offset: 0x%02x p_filesz: 0x%04x", p_offset, p_filesz)
self.f.seek(p_offset)
buf = self.f.read(p_filesz)
if len(buf) != p_filesz:
raise ValueError("failed to read program header content")
return Phdr(p_type, p_offset, p_vaddr, p_paddr, p_filesz, buf)
return Phdr(p_type, p_offset, p_vaddr, p_paddr, p_filesz, buf, p_flags, p_memsz)
@property
def program_headers(self):
@@ -345,8 +365,6 @@ class ELF:
else:
raise NotImplementedError()
logger.debug("sh:sh_offset: 0x%02x sh_size: 0x%04x", sh_offset, sh_size)
self.f.seek(sh_offset)
buf = self.f.read(sh_size)
if len(buf) != sh_size:
@@ -362,6 +380,10 @@ class ELF:
except ValueError:
continue
@property
def shstrtab(self) -> Shdr:
return self.parse_section_header(self.e_shstrndx)
@property
def linker(self):
PT_INTERP = 0x3
@@ -372,7 +394,7 @@ class ELF:
return read_cstr(phdr.buf, 0)
@property
def versions_needed(self) -> Dict[str, Set[str]]:
def versions_needed(self) -> dict[str, set[str]]:
# symbol version requirements are stored in the .gnu.version_r section,
# which has type SHT_GNU_verneed (0x6ffffffe).
#
@@ -430,7 +452,7 @@ class ELF:
return {}
@property
def dynamic_entries(self) -> Iterator[Tuple[int, int]]:
def dynamic_entries(self) -> Iterator[tuple[int, int]]:
"""
read the entries from the dynamic section,
yielding the tag and value for each entry.
@@ -525,7 +547,7 @@ class ELF:
logger.warning("failed to read DT_NEEDED entry: %s", str(e))
@property
def symtab(self) -> Optional[Tuple[Shdr, Shdr]]:
def symtab(self) -> Optional[tuple[Shdr, Shdr]]:
"""
fetch the Shdr for the symtab and the associated strtab.
"""
@@ -660,7 +682,7 @@ class SymTab:
symtab: Shdr,
strtab: Shdr,
) -> None:
self.symbols: List[Symbol] = []
self.symbols: list[Symbol] = []
self.symtab = symtab
self.strtab = strtab
@@ -709,17 +731,17 @@ class SymTab:
yield from self.symbols
@classmethod
def from_Elf(cls, ElfBinary) -> Optional["SymTab"]:
endian = "<" if ElfBinary.getEndian() == 0 else ">"
bitness = ElfBinary.bits
def from_viv(cls, elf: "Elf.Elf") -> Optional["SymTab"]:
endian = "<" if elf.getEndian() == 0 else ">"
bitness = elf.bits
SHT_SYMTAB = 0x2
for section in ElfBinary.sections:
if section.sh_info & SHT_SYMTAB:
strtab_section = ElfBinary.sections[section.sh_link]
sh_symtab = Shdr.from_viv(section, ElfBinary.readAtOffset(section.sh_offset, section.sh_size))
for section in elf.sections:
if section.sh_type == SHT_SYMTAB:
strtab_section = elf.sections[section.sh_link]
sh_symtab = Shdr.from_viv(section, elf.readAtOffset(section.sh_offset, section.sh_size))
sh_strtab = Shdr.from_viv(
strtab_section, ElfBinary.readAtOffset(strtab_section.sh_offset, strtab_section.sh_size)
strtab_section, elf.readAtOffset(strtab_section.sh_offset, strtab_section.sh_size)
)
try:
@@ -764,6 +786,11 @@ def guess_os_from_ph_notes(elf: ELF) -> Optional[OS]:
elif note.name == "FreeBSD":
logger.debug("note owner: %s", "FREEBSD")
return OS.FREEBSD
elif note.name == "Android":
logger.debug("note owner: %s", "Android")
# see the following for parsing the structure:
# https://android.googlesource.com/platform/ndk/+/master/parse_elfnote.py
return OS.ANDROID
elif note.name == "GNU":
abi_tag = note.abi_tag
if abi_tag:
@@ -808,6 +835,52 @@ def guess_os_from_sh_notes(elf: ELF) -> Optional[OS]:
return None
def guess_os_from_ident_directive(elf: ELF) -> Optional[OS]:
# GCC inserts the GNU version via an .ident directive
# that gets stored in a section named ".comment".
# look at the version and recognize common OSes.
#
# assume the GCC version matches the target OS version,
# which I guess could be wrong during cross-compilation?
# therefore, don't rely on this if possible.
#
# https://stackoverflow.com/q/6263425
# https://gcc.gnu.org/onlinedocs/cpp/Other-Directives.html
SHT_PROGBITS = 0x1
for shdr in elf.section_headers:
if shdr.type != SHT_PROGBITS:
continue
if shdr.get_name(elf) != ".comment":
continue
try:
comment = shdr.buf.decode("utf-8")
except ValueError:
continue
if "GCC:" not in comment:
continue
logger.debug(".ident: %s", comment)
# these values come from our testfiles, like:
# rg -a "GCC: " tests/data/
if "Debian" in comment:
return OS.LINUX
elif "Ubuntu" in comment:
return OS.LINUX
elif "Red Hat" in comment:
return OS.LINUX
elif "Alpine" in comment:
return OS.LINUX
elif "Android" in comment:
return OS.ANDROID
return None
def guess_os_from_linker(elf: ELF) -> Optional[OS]:
# search for recognizable dynamic linkers (interpreters)
# for example, on linux, we see file paths like: /lib64/ld-linux-x86-64.so.2
@@ -843,8 +916,10 @@ def guess_os_from_abi_versions_needed(elf: ELF) -> Optional[OS]:
return OS.HURD
else:
# we don't have any good guesses based on versions needed
pass
# in practice, Hurd isn't a common/viable OS,
# so this is almost certain to be Linux,
# so lets just make that guess.
return OS.LINUX
return None
@@ -855,6 +930,10 @@ def guess_os_from_needed_dependencies(elf: ELF) -> Optional[OS]:
return OS.HURD
if needed.startswith("libhurduser.so"):
return OS.HURD
if needed.startswith("libandroid.so"):
return OS.ANDROID
if needed.startswith("liblog.so"):
return OS.ANDROID
return None
@@ -881,14 +960,509 @@ def guess_os_from_symtab(elf: ELF) -> Optional[OS]:
for os, hints in keywords.items():
if any(hint in sym_name for hint in hints):
logger.debug("symtab: %s looks like %s", sym_name, os)
return os
return None
def is_go_binary(elf: ELF) -> bool:
for shdr in elf.section_headers:
if shdr.get_name(elf) == ".note.go.buildid":
logger.debug("go buildinfo: found section .note.go.buildid")
return True
# The `go version` command enumerates sections for the name `.go.buildinfo`
# (in addition to looking for the BUILDINFO_MAGIC) to check if an executable is go or not.
# See references to the `errNotGoExe` error here:
# https://github.com/golang/go/blob/master/src/debug/buildinfo/buildinfo.go#L41
for shdr in elf.section_headers:
if shdr.get_name(elf) == ".go.buildinfo":
logger.debug("go buildinfo: found section .go.buildinfo")
return True
# other strategy used by FLOSS: search for known runtime strings.
# https://github.com/mandiant/flare-floss/blob/b2ca8adfc5edf278861dd6bff67d73da39683b46/floss/language/identify.py#L88
return False
def get_go_buildinfo_data(elf: ELF) -> Optional[bytes]:
for shdr in elf.section_headers:
if shdr.get_name(elf) == ".go.buildinfo":
logger.debug("go buildinfo: found section .go.buildinfo")
return shdr.buf
PT_LOAD = 0x1
PF_X = 1
PF_W = 2
for phdr in elf.program_headers:
if phdr.type != PT_LOAD:
continue
if (phdr.flags & (PF_X | PF_W)) == PF_W:
logger.debug("go buildinfo: found data segment")
return phdr.buf
return None
def read_data(elf: ELF, rva: int, size: int) -> Optional[bytes]:
# ELF segments are for runtime data,
# ELF sections are for link-time data.
# So we want to read Program Headers/Segments.
for phdr in elf.program_headers:
if phdr.vaddr <= rva < phdr.vaddr + phdr.memsz:
segment_data = phdr.buf
# pad the section with NULLs
# assume page alignment is already handled.
# might need more hardening here.
if len(segment_data) < phdr.memsz:
segment_data += b"\x00" * (phdr.memsz - len(segment_data))
segment_offset = rva - phdr.vaddr
return segment_data[segment_offset : segment_offset + size]
return None
def read_go_slice(elf: ELF, rva: int) -> Optional[bytes]:
if elf.bitness == 32:
struct_size = 8
struct_format = elf.endian + "II"
elif elf.bitness == 64:
struct_size = 16
struct_format = elf.endian + "QQ"
else:
raise ValueError("invalid psize")
struct_buf = read_data(elf, rva, struct_size)
if not struct_buf:
return None
addr, length = struct.unpack_from(struct_format, struct_buf, 0)
return read_data(elf, addr, length)
def guess_os_from_go_buildinfo(elf: ELF) -> Optional[OS]:
"""
In a binary compiled by Go, the buildinfo structure may contain
metadata about the build environment, including the configured
GOOS, which specifies the target operating system.
Search for and parse the buildinfo structure,
which may be found in the .go.buildinfo section,
and often contains this metadata inline. Otherwise,
follow a few byte slices to the relevant information.
This strategy is derived from GoReSym.
"""
buf = get_go_buildinfo_data(elf)
if not buf:
logger.debug("go buildinfo: no buildinfo section")
return None
assert isinstance(buf, bytes)
# The build info blob left by the linker is identified by
# a 16-byte header, consisting of:
# - buildInfoMagic (14 bytes),
# - the binary's pointer size (1 byte), and
# - whether the binary is big endian (1 byte).
#
# Then:
# - virtual address to Go string: runtime.buildVersion
# - virtual address to Go string: runtime.modinfo
#
# On 32-bit platforms, the last 8 bytes are unused.
#
# If the endianness has the 2 bit set, then the pointers are zero,
# and the 32-byte header is followed by varint-prefixed string data
# for the two string values we care about.
# https://github.com/mandiant/GoReSym/blob/0860a1b1b4f3495e9fb7e71eb4386bf3e0a7c500/buildinfo/buildinfo.go#L185-L193
BUILDINFO_MAGIC = b"\xFF Go buildinf:"
try:
index = buf.index(BUILDINFO_MAGIC)
except ValueError:
logger.debug("go buildinfo: no buildinfo magic")
return None
psize, flags = struct.unpack_from("<bb", buf, index + len(BUILDINFO_MAGIC))
assert psize in (4, 8)
is_big_endian = flags & 0b01
has_inline_strings = flags & 0b10
logger.debug("go buildinfo: psize: %d big endian: %s inline: %s", psize, is_big_endian, has_inline_strings)
GOOS_TO_OS = {
b"aix": OS.AIX,
b"android": OS.ANDROID,
b"dragonfly": OS.DRAGONFLYBSD,
b"freebsd": OS.FREEBSD,
b"hurd": OS.HURD,
b"illumos": OS.ILLUMOS,
b"linux": OS.LINUX,
b"netbsd": OS.NETBSD,
b"openbsd": OS.OPENBSD,
b"solaris": OS.SOLARIS,
b"zos": OS.ZOS,
b"windows": None, # PE format
b"plan9": None, # a.out format
b"ios": None, # Mach-O format
b"darwin": None, # Mach-O format
b"nacl": None, # dropped in GO 1.14
b"js": None,
}
if has_inline_strings:
# This is the common case/path. Most samples will have an inline GOOS string.
#
# To find samples on VT, use these VTGrep searches:
#
# content: {ff 20 47 6f 20 62 75 69 6c 64 69 6e 66 3a 04 02}
# content: {ff 20 47 6f 20 62 75 69 6c 64 69 6e 66 3a 08 02}
# If present, the GOOS key will be found within
# the current buildinfo data region.
#
# Brute force the k-v pair, like `GOOS=linux`,
# rather than try to parse the data, which would be fragile.
for key, os in GOOS_TO_OS.items():
if (b"GOOS=" + key) in buf:
logger.debug("go buildinfo: found os: %s", os)
return os
else:
# This is the uncommon path. Most samples will have an inline GOOS string.
#
# To find samples on VT, use the referenced VTGrep content searches.
info_format = {
# content: {ff 20 47 6f 20 62 75 69 6c 64 69 6e 66 3a 04 00}
# like: 71e617e5cc7fda89bf67422ff60f437e9d54622382c5ed6ff31f75e601f9b22e
# in which the modinfo doesn't have GOOS.
(4, False): "<II",
# content: {ff 20 47 6f 20 62 75 69 6c 64 69 6e 66 3a 08 00}
# like: 93d3b3e2a904c6c909e20f2f76c3c2e8d0c81d535eb46e5493b5701f461816c3
# in which the modinfo doesn't have GOOS.
(8, False): "<QQ",
# content: {ff 20 47 6f 20 62 75 69 6c 64 69 6e 66 3a 04 01}
# (no matches on VT today)
(4, True): ">II",
# content: {ff 20 47 6f 20 62 75 69 6c 64 69 6e 66 3a 08 01}
# like: d44ba497964050c0e3dd2a192c511e4c3c4f17717f0322a554d64b797ee4690a
# in which the modinfo doesn't have GOOS.
(8, True): ">QQ",
}
build_version_address, modinfo_address = struct.unpack_from(
info_format[(psize, is_big_endian)], buf, index + 0x10
)
logger.debug("go buildinfo: build version address: 0x%x", build_version_address)
logger.debug("go buildinfo: modinfo address: 0x%x", modinfo_address)
build_version = read_go_slice(elf, build_version_address)
if build_version:
logger.debug("go buildinfo: build version: %s", build_version.decode("utf-8"))
modinfo = read_go_slice(elf, modinfo_address)
if modinfo:
if modinfo[-0x11] == ord("\n"):
# Strip module framing: sentinel strings delimiting the module info.
# These are cmd/go/internal/modload/build.infoStart and infoEnd.
# Which should probably be:
# infoStart, _ = hex.DecodeString("3077af0c9274080241e1c107e6d618e6")
# infoEnd, _ = hex.DecodeString("f932433186182072008242104116d8f2")
modinfo = modinfo[0x10:-0x10]
logger.debug("go buildinfo: modinfo: %s", modinfo.decode("utf-8"))
if not modinfo:
return None
for key, os in GOOS_TO_OS.items():
# Brute force the k-v pair, like `GOOS=linux`,
# rather than try to parse the data, which would be fragile.
if (b"GOOS=" + key) in modinfo:
logger.debug("go buildinfo: found os: %s", os)
return os
return None
def guess_os_from_go_source(elf: ELF) -> Optional[OS]:
"""
In a binary compiled by Go, runtime metadata may contain
references to the source filenames, including the
src/runtime/os_* files, whose name indicates the
target operating system.
Confirm the given ELF seems to be built by Go,
and then look for strings that look like
Go source filenames.
This strategy is derived from GoReSym.
"""
if not is_go_binary(elf):
return None
for phdr in elf.program_headers:
buf = phdr.buf
NEEDLE_OS = b"/src/runtime/os_"
try:
index = buf.index(NEEDLE_OS)
except ValueError:
continue
rest = buf[index + len(NEEDLE_OS) : index + len(NEEDLE_OS) + 32]
filename = rest.partition(b".go")[0].decode("utf-8")
logger.debug("go source: filename: /src/runtime/os_%s.go", filename)
# via: https://cs.opensource.google/go/go/+/master:src/runtime/;bpv=1;bpt=0
# candidates today:
# - aix
# - android
# - darwin
# - darwin_arm64
# - dragonfly
# - freebsd
# - freebsd2
# - freebsd_amd64
# - freebsd_arm
# - freebsd_arm64
# - freebsd_noauxv
# - freebsd_riscv64
# - illumos
# - js
# - linux
# - linux_arm
# - linux_arm64
# - linux_be64
# - linux_generic
# - linux_loong64
# - linux_mips64x
# - linux_mipsx
# - linux_noauxv
# - linux_novdso
# - linux_ppc64x
# - linux_riscv64
# - linux_s390x
# - linux_x86
# - netbsd
# - netbsd_386
# - netbsd_amd64
# - netbsd_arm
# - netbsd_arm64
# - nonopenbsd
# - only_solaris
# - openbsd
# - openbsd_arm
# - openbsd_arm64
# - openbsd_libc
# - openbsd_mips64
# - openbsd_syscall
# - openbsd_syscall1
# - openbsd_syscall2
# - plan9
# - plan9_arm
# - solaris
# - unix
# - unix_nonlinux
# - wasip1
# - wasm
# - windows
# - windows_arm
# - windows_arm64
OS_FILENAME_TO_OS = {
"aix": OS.AIX,
"android": OS.ANDROID,
"dragonfly": OS.DRAGONFLYBSD,
"freebsd": OS.FREEBSD,
"freebsd2": OS.FREEBSD,
"freebsd_": OS.FREEBSD,
"illumos": OS.ILLUMOS,
"linux": OS.LINUX,
"netbsd": OS.NETBSD,
"only_solaris": OS.SOLARIS,
"openbsd": OS.OPENBSD,
"solaris": OS.SOLARIS,
"unix_nonlinux": OS.UNIX,
}
for prefix, os in OS_FILENAME_TO_OS.items():
if filename.startswith(prefix):
return os
for phdr in elf.program_headers:
buf = phdr.buf
NEEDLE_RT0 = b"/src/runtime/rt0_"
try:
index = buf.index(NEEDLE_RT0)
except ValueError:
continue
rest = buf[index + len(NEEDLE_RT0) : index + len(NEEDLE_RT0) + 32]
filename = rest.partition(b".s")[0].decode("utf-8")
logger.debug("go source: filename: /src/runtime/rt0_%s.s", filename)
# via: https://cs.opensource.google/go/go/+/master:src/runtime/;bpv=1;bpt=0
# candidates today:
# - aix_ppc64
# - android_386
# - android_amd64
# - android_arm
# - android_arm64
# - darwin_amd64
# - darwin_arm64
# - dragonfly_amd64
# - freebsd_386
# - freebsd_amd64
# - freebsd_arm
# - freebsd_arm64
# - freebsd_riscv64
# - illumos_amd64
# - ios_amd64
# - ios_arm64
# - js_wasm
# - linux_386
# - linux_amd64
# - linux_arm
# - linux_arm64
# - linux_loong64
# - linux_mips64x
# - linux_mipsx
# - linux_ppc64
# - linux_ppc64le
# - linux_riscv64
# - linux_s390x
# - netbsd_386
# - netbsd_amd64
# - netbsd_arm
# - netbsd_arm64
# - openbsd_386
# - openbsd_amd64
# - openbsd_arm
# - openbsd_arm64
# - openbsd_mips64
# - openbsd_ppc64
# - openbsd_riscv64
# - plan9_386
# - plan9_amd64
# - plan9_arm
# - solaris_amd64
# - wasip1_wasm
# - windows_386
# - windows_amd64
# - windows_arm
# - windows_arm64
RT0_FILENAME_TO_OS = {
"aix": OS.AIX,
"android": OS.ANDROID,
"dragonfly": OS.DRAGONFLYBSD,
"freebsd": OS.FREEBSD,
"illumos": OS.ILLUMOS,
"linux": OS.LINUX,
"netbsd": OS.NETBSD,
"openbsd": OS.OPENBSD,
"solaris": OS.SOLARIS,
}
for prefix, os in RT0_FILENAME_TO_OS.items():
if filename.startswith(prefix):
return os
return None
def guess_os_from_vdso_strings(elf: ELF) -> Optional[OS]:
"""
The "vDSO" (virtual dynamic shared object) is a small shared
library that the kernel automatically maps into the address space
of all user-space applications.
Some statically linked executables include small dynamic linker
routines that finds these vDSO symbols, using the ASCII
symbol name and version. We can therefore recognize the pairs
(symbol, version) to guess the binary targets Linux.
"""
for phdr in elf.program_headers:
buf = phdr.buf
# We don't really use the arch, but its interesting for documentation
# I suppose we could restrict the arch here to what's in the ELF header,
# but that's even more work. Let's see if this is sufficient.
for arch, symbol, version in (
# via: https://man7.org/linux/man-pages/man7/vdso.7.html
("arm", b"__vdso_gettimeofday", b"LINUX_2.6"),
("arm", b"__vdso_clock_gettime", b"LINUX_2.6"),
("aarch64", b"__kernel_rt_sigreturn", b"LINUX_2.6.39"),
("aarch64", b"__kernel_gettimeofday", b"LINUX_2.6.39"),
("aarch64", b"__kernel_clock_gettime", b"LINUX_2.6.39"),
("aarch64", b"__kernel_clock_getres", b"LINUX_2.6.39"),
("mips", b"__kernel_gettimeofday", b"LINUX_2.6"),
("mips", b"__kernel_clock_gettime", b"LINUX_2.6"),
("ia64", b"__kernel_sigtramp", b"LINUX_2.5"),
("ia64", b"__kernel_syscall_via_break", b"LINUX_2.5"),
("ia64", b"__kernel_syscall_via_epc", b"LINUX_2.5"),
("ppc/32", b"__kernel_clock_getres", b"LINUX_2.6.15"),
("ppc/32", b"__kernel_clock_gettime", b"LINUX_2.6.15"),
("ppc/32", b"__kernel_clock_gettime64", b"LINUX_5.11"),
("ppc/32", b"__kernel_datapage_offset", b"LINUX_2.6.15"),
("ppc/32", b"__kernel_get_syscall_map", b"LINUX_2.6.15"),
("ppc/32", b"__kernel_get_tbfreq", b"LINUX_2.6.15"),
("ppc/32", b"__kernel_getcpu", b"LINUX_2.6.15"),
("ppc/32", b"__kernel_gettimeofday", b"LINUX_2.6.15"),
("ppc/32", b"__kernel_sigtramp_rt32", b"LINUX_2.6.15"),
("ppc/32", b"__kernel_sigtramp32", b"LINUX_2.6.15"),
("ppc/32", b"__kernel_sync_dicache", b"LINUX_2.6.15"),
("ppc/32", b"__kernel_sync_dicache_p5", b"LINUX_2.6.15"),
("ppc/64", b"__kernel_clock_getres", b"LINUX_2.6.15"),
("ppc/64", b"__kernel_clock_gettime", b"LINUX_2.6.15"),
("ppc/64", b"__kernel_datapage_offset", b"LINUX_2.6.15"),
("ppc/64", b"__kernel_get_syscall_map", b"LINUX_2.6.15"),
("ppc/64", b"__kernel_get_tbfreq", b"LINUX_2.6.15"),
("ppc/64", b"__kernel_getcpu", b"LINUX_2.6.15"),
("ppc/64", b"__kernel_gettimeofday", b"LINUX_2.6.15"),
("ppc/64", b"__kernel_sigtramp_rt64", b"LINUX_2.6.15"),
("ppc/64", b"__kernel_sync_dicache", b"LINUX_2.6.15"),
("ppc/64", b"__kernel_sync_dicache_p5", b"LINUX_2.6.15"),
("riscv", b"__vdso_rt_sigreturn", b"LINUX_4.15"),
("riscv", b"__vdso_gettimeofday", b"LINUX_4.15"),
("riscv", b"__vdso_clock_gettime", b"LINUX_4.15"),
("riscv", b"__vdso_clock_getres", b"LINUX_4.15"),
("riscv", b"__vdso_getcpu", b"LINUX_4.15"),
("riscv", b"__vdso_flush_icache", b"LINUX_4.15"),
("s390", b"__kernel_clock_getres", b"LINUX_2.6.29"),
("s390", b"__kernel_clock_gettime", b"LINUX_2.6.29"),
("s390", b"__kernel_gettimeofday", b"LINUX_2.6.29"),
("superh", b"__kernel_rt_sigreturn", b"LINUX_2.6"),
("superh", b"__kernel_sigreturn", b"LINUX_2.6"),
("superh", b"__kernel_vsyscall", b"LINUX_2.6"),
("i386", b"__kernel_sigreturn", b"LINUX_2.5"),
("i386", b"__kernel_rt_sigreturn", b"LINUX_2.5"),
("i386", b"__kernel_vsyscall", b"LINUX_2.5"),
("i386", b"__vdso_clock_gettime", b"LINUX_2.6"),
("i386", b"__vdso_gettimeofday", b"LINUX_2.6"),
("i386", b"__vdso_time", b"LINUX_2.6"),
("x86-64", b"__vdso_clock_gettime", b"LINUX_2.6"),
("x86-64", b"__vdso_getcpu", b"LINUX_2.6"),
("x86-64", b"__vdso_gettimeofday", b"LINUX_2.6"),
("x86-64", b"__vdso_time", b"LINUX_2.6"),
("x86/32", b"__vdso_clock_gettime", b"LINUX_2.6"),
("x86/32", b"__vdso_getcpu", b"LINUX_2.6"),
("x86/32", b"__vdso_gettimeofday", b"LINUX_2.6"),
("x86/32", b"__vdso_time", b"LINUX_2.6"),
):
if symbol in buf and version in buf:
logger.debug("vdso string: %s %s %s", arch, symbol.decode("ascii"), version.decode("ascii"))
return OS.LINUX
return None
def detect_elf_os(f) -> str:
"""
f: type Union[BinaryIO, IDAIO]
f: type Union[BinaryIO, IDAIO, GHIDRAIO]
"""
try:
elf = ELF(f)
@@ -917,6 +1491,13 @@ def detect_elf_os(f) -> str:
logger.warning("Error guessing OS from section header notes: %s", e)
sh_notes_guess = None
try:
ident_guess = guess_os_from_ident_directive(elf)
logger.debug("guess: .ident: %s", ident_guess)
except Exception as e:
logger.warning("Error guessing OS from .ident directive: %s", e)
ident_guess = None
try:
linker_guess = guess_os_from_linker(elf)
logger.debug("guess: linker: %s", linker_guess)
@@ -945,6 +1526,27 @@ def detect_elf_os(f) -> str:
logger.warning("Error guessing OS from symbol table: %s", e)
symtab_guess = None
try:
goos_guess = guess_os_from_go_buildinfo(elf)
logger.debug("guess: Go buildinfo: %s", goos_guess)
except Exception as e:
logger.warning("Error guessing OS from Go buildinfo: %s", e)
goos_guess = None
try:
gosrc_guess = guess_os_from_go_source(elf)
logger.debug("guess: Go source: %s", gosrc_guess)
except Exception as e:
logger.warning("Error guessing OS from Go source path: %s", e)
gosrc_guess = None
try:
vdso_guess = guess_os_from_vdso_strings(elf)
logger.debug("guess: vdso strings: %s", vdso_guess)
except Exception as e:
logger.warning("Error guessing OS from vdso strings: %s", e)
symtab_guess = None
ret = None
if osabi_guess:
@@ -968,6 +1570,24 @@ def detect_elf_os(f) -> str:
elif symtab_guess:
ret = symtab_guess
elif goos_guess:
ret = goos_guess
elif gosrc_guess:
# prefer goos_guess to this method,
# which is just string interpretation.
ret = gosrc_guess
elif ident_guess:
# at the bottom because we don't trust this too much
# due to potential for bugs with cross-compilation.
ret = ident_guess
elif vdso_guess:
# at the bottom because this is just scanning strings,
# which isn't very authoritative.
ret = vdso_guess
return ret.value if ret is not None else "unknown"

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2023 Mandiant, Inc. All Rights Reserved.
# Copyright (C) 2021 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
@@ -7,25 +7,22 @@
# See the License for the specific language governing permissions and limitations under the License.
import io
import logging
from typing import Tuple, Iterator
from typing import Iterator
from pathlib import Path
from elftools.elf.elffile import ELFFile, SymbolTableSection
from elftools.elf.elffile import ELFFile, DynamicSegment, SymbolTableSection
import capa.features.extractors.common
from capa.features.file import Import, Section
from capa.features.file import Export, Import, Section
from capa.features.common import OS, FORMAT_ELF, Arch, Format, Feature
from capa.features.address import NO_ADDRESS, FileOffsetAddress, AbsoluteVirtualAddress
from capa.features.extractors.base_extractor import FeatureExtractor
from capa.features.extractors.base_extractor import SampleHashes, StaticFeatureExtractor
logger = logging.getLogger(__name__)
def extract_file_import_names(elf, **kwargs):
# see https://github.com/eliben/pyelftools/blob/0664de05ed2db3d39041e2d51d19622a8ef4fb0f/scripts/readelf.py#L372
symbol_tables = [(idx, s) for idx, s in enumerate(elf.iter_sections()) if isinstance(s, SymbolTableSection)]
for _, section in symbol_tables:
def extract_file_export_names(elf: ELFFile, **kwargs):
for section in elf.iter_sections():
if not isinstance(section, SymbolTableSection):
continue
@@ -35,14 +32,101 @@ def extract_file_import_names(elf, **kwargs):
logger.debug("Symbol table '%s' contains %s entries:", section.name, section.num_symbols())
for _, symbol in enumerate(section.iter_symbols()):
if symbol.name and symbol.entry.st_info.type == "STT_FUNC":
# TODO(williballenthin): extract symbol address
# https://github.com/mandiant/capa/issues/1608
yield Import(symbol.name), FileOffsetAddress(0x0)
for symbol in section.iter_symbols():
# The following conditions are based on the following article
# http://www.m4b.io/elf/export/binary/analysis/2015/05/25/what-is-an-elf-export.html
if not symbol.name:
continue
if symbol.entry.st_info.type not in ["STT_FUNC", "STT_OBJECT", "STT_IFUNC"]:
continue
if symbol.entry.st_value == 0:
continue
if symbol.entry.st_shndx == "SHN_UNDEF":
continue
yield Export(symbol.name), AbsoluteVirtualAddress(symbol.entry.st_value)
for segment in elf.iter_segments():
if not isinstance(segment, DynamicSegment):
continue
tab_ptr, tab_offset = segment.get_table_offset("DT_SYMTAB")
if tab_ptr is None or tab_offset is None:
logger.debug("Dynamic segment doesn't contain DT_SYMTAB")
continue
logger.debug("Dynamic segment contains %s symbols: ", segment.num_symbols())
for symbol in segment.iter_symbols():
# The following conditions are based on the following article
# http://www.m4b.io/elf/export/binary/analysis/2015/05/25/what-is-an-elf-export.html
if not symbol.name:
continue
if symbol.entry.st_info.type not in ["STT_FUNC", "STT_OBJECT", "STT_IFUNC"]:
continue
if symbol.entry.st_value == 0:
continue
if symbol.entry.st_shndx == "SHN_UNDEF":
continue
yield Export(symbol.name), AbsoluteVirtualAddress(symbol.entry.st_value)
def extract_file_section_names(elf, **kwargs):
def extract_file_import_names(elf: ELFFile, **kwargs):
# Create a dictionary to store symbol names by their index
symbol_names = {}
# Extract symbol names and store them in the dictionary
for segment in elf.iter_segments():
if not isinstance(segment, DynamicSegment):
continue
tab_ptr, tab_offset = segment.get_table_offset("DT_SYMTAB")
if tab_ptr is None or tab_offset is None:
logger.debug("Dynamic segment doesn't contain DT_SYMTAB")
continue
for _, symbol in enumerate(segment.iter_symbols()):
# The following conditions are based on the following article
# http://www.m4b.io/elf/export/binary/analysis/2015/05/25/what-is-an-elf-export.html
if not symbol.name:
continue
if symbol.entry.st_info.type not in ["STT_FUNC", "STT_OBJECT", "STT_IFUNC"]:
continue
if symbol.entry.st_value != 0:
continue
if symbol.entry.st_shndx != "SHN_UNDEF":
continue
if symbol.entry.st_name == 0:
continue
symbol_names[_] = symbol.name
for segment in elf.iter_segments():
if not isinstance(segment, DynamicSegment):
continue
relocation_tables = segment.get_relocation_tables()
logger.debug("Dynamic Segment contains %s relocation tables:", len(relocation_tables))
for relocation_table in relocation_tables.values():
relocations = []
for i in range(relocation_table.num_relocations()):
try:
relocations.append(relocation_table.get_relocation(i))
except TypeError:
# ELF is corrupt and the relocation table is invalid,
# so stop processing it.
break
for relocation in relocations:
# Extract the symbol name from the symbol table using the symbol index in the relocation
if relocation["r_info_sym"] not in symbol_names:
continue
yield Import(symbol_names[relocation["r_info_sym"]]), FileOffsetAddress(relocation["r_offset"])
def extract_file_section_names(elf: ELFFile, **kwargs):
for section in elf.iter_sections():
if section.name:
yield Section(section.name), AbsoluteVirtualAddress(section.header.sh_addr)
@@ -54,7 +138,7 @@ def extract_file_strings(buf, **kwargs):
yield from capa.features.extractors.common.extract_file_strings(buf)
def extract_file_os(elf, buf, **kwargs):
def extract_file_os(elf: ELFFile, buf, **kwargs):
# our current approach does not always get an OS value, e.g. for packed samples
# for file limitation purposes, we're more lax here
try:
@@ -68,25 +152,28 @@ def extract_file_format(**kwargs):
yield Format(FORMAT_ELF), NO_ADDRESS
def extract_file_arch(elf, **kwargs):
def extract_file_arch(elf: ELFFile, **kwargs):
arch = elf.get_machine_arch()
if arch == "x86":
yield Arch("i386"), NO_ADDRESS
elif arch == "x64":
yield Arch("amd64"), NO_ADDRESS
elif arch == "ARM":
yield Arch("arm"), NO_ADDRESS
elif arch == "AArch64":
yield Arch("aarch64"), NO_ADDRESS
else:
logger.warning("unsupported architecture: %s", arch)
def extract_file_features(elf: ELFFile, buf: bytes) -> Iterator[Tuple[Feature, int]]:
def extract_file_features(elf: ELFFile, buf: bytes) -> Iterator[tuple[Feature, int]]:
for file_handler in FILE_HANDLERS:
for feature, addr in file_handler(elf=elf, buf=buf): # type: ignore
yield feature, addr
FILE_HANDLERS = (
# TODO(williballenthin): implement extract_file_export_names
# https://github.com/mandiant/capa/issues/1607
extract_file_export_names,
extract_file_import_names,
extract_file_section_names,
extract_file_strings,
@@ -95,7 +182,7 @@ FILE_HANDLERS = (
)
def extract_global_features(elf: ELFFile, buf: bytes) -> Iterator[Tuple[Feature, int]]:
def extract_global_features(elf: ELFFile, buf: bytes) -> Iterator[tuple[Feature, int]]:
for global_handler in GLOBAL_HANDLERS:
for feature, addr in global_handler(elf=elf, buf=buf): # type: ignore
yield feature, addr
@@ -107,9 +194,9 @@ GLOBAL_HANDLERS = (
)
class ElfFeatureExtractor(FeatureExtractor):
class ElfFeatureExtractor(StaticFeatureExtractor):
def __init__(self, path: Path):
super().__init__()
super().__init__(SampleHashes.from_bytes(path.read_bytes()))
self.path: Path = path
self.elf = ELFFile(io.BytesIO(path.read_bytes()))

View File

@@ -0,0 +1,152 @@
# Copyright (C) 2023 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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.
import string
import struct
from typing import Iterator
import ghidra
from ghidra.program.model.lang import OperandType
import capa.features.extractors.ghidra.helpers
from capa.features.common import Feature, Characteristic
from capa.features.address import Address
from capa.features.basicblock import BasicBlock
from capa.features.extractors.helpers import MIN_STACKSTRING_LEN
from capa.features.extractors.base_extractor import BBHandle, FunctionHandle
def get_printable_len(op: ghidra.program.model.scalar.Scalar) -> int:
"""Return string length if all operand bytes are ascii or utf16-le printable"""
op_bit_len = op.bitLength()
op_byte_len = op_bit_len // 8
op_val = op.getValue()
if op_bit_len == 8:
chars = struct.pack("<B", op_val & 0xFF)
elif op_bit_len == 16:
chars = struct.pack("<H", op_val & 0xFFFF)
elif op_bit_len == 32:
chars = struct.pack("<I", op_val & 0xFFFFFFFF)
elif op_bit_len == 64:
chars = struct.pack("<Q", op_val & 0xFFFFFFFFFFFFFFFF)
else:
raise ValueError(f"Unhandled operand data type 0x{op_bit_len:x}.")
def is_printable_ascii(chars_: bytes):
return all(c < 127 and chr(c) in string.printable for c in chars_)
def is_printable_utf16le(chars_: bytes):
if all(c == 0x00 for c in chars_[1::2]):
return is_printable_ascii(chars_[::2])
if is_printable_ascii(chars):
return op_byte_len
if is_printable_utf16le(chars):
return op_byte_len
return 0
def is_mov_imm_to_stack(insn: ghidra.program.database.code.InstructionDB) -> bool:
"""verify instruction moves immediate onto stack"""
# Ghidra will Bitwise OR the OperandTypes to assign multiple
# i.e., the first operand is a stackvar (dynamically allocated),
# and the second is a scalar value (single int/char/float/etc.)
mov_its_ops = [(OperandType.ADDRESS | OperandType.DYNAMIC), OperandType.SCALAR]
found = False
# MOV dword ptr [EBP + local_*], 0x65
if insn.getMnemonicString().startswith("MOV"):
found = all(insn.getOperandType(i) == mov_its_ops[i] for i in range(2))
return found
def bb_contains_stackstring(bb: ghidra.program.model.block.CodeBlock) -> bool:
"""check basic block for stackstring indicators
true if basic block contains enough moves of constant bytes to the stack
"""
count = 0
for insn in currentProgram().getListing().getInstructions(bb, True): # type: ignore [name-defined] # noqa: F821
if is_mov_imm_to_stack(insn):
count += get_printable_len(insn.getScalar(1))
if count > MIN_STACKSTRING_LEN:
return True
return False
def _bb_has_tight_loop(bb: ghidra.program.model.block.CodeBlock):
"""
parse tight loops, true if last instruction in basic block branches to bb start
"""
# Reverse Ordered, first InstructionDB
last_insn = currentProgram().getListing().getInstructions(bb, False).next() # type: ignore [name-defined] # noqa: F821
if last_insn.getFlowType().isJump():
return last_insn.getAddress(0) == bb.getMinAddress()
return False
def extract_bb_stackstring(fh: FunctionHandle, bbh: BBHandle) -> Iterator[tuple[Feature, Address]]:
"""extract stackstring indicators from basic block"""
bb: ghidra.program.model.block.CodeBlock = bbh.inner
if bb_contains_stackstring(bb):
yield Characteristic("stack string"), bbh.address
def extract_bb_tight_loop(fh: FunctionHandle, bbh: BBHandle) -> Iterator[tuple[Feature, Address]]:
"""check basic block for tight loop indicators"""
bb: ghidra.program.model.block.CodeBlock = bbh.inner
if _bb_has_tight_loop(bb):
yield Characteristic("tight loop"), bbh.address
BASIC_BLOCK_HANDLERS = (
extract_bb_tight_loop,
extract_bb_stackstring,
)
def extract_features(fh: FunctionHandle, bbh: BBHandle) -> Iterator[tuple[Feature, Address]]:
"""
extract features from the given basic block.
args:
bb: the basic block to process.
yields:
tuple[Feature, int]: the features and their location found in this basic block.
"""
yield BasicBlock(), bbh.address
for bb_handler in BASIC_BLOCK_HANDLERS:
for feature, addr in bb_handler(fh, bbh):
yield feature, addr
def main():
features = []
from capa.features.extractors.ghidra.extractor import GhidraFeatureExtractor
for fh in GhidraFeatureExtractor().get_functions():
for bbh in capa.features.extractors.ghidra.helpers.get_function_blocks(fh):
features.extend(list(extract_features(fh, bbh)))
import pprint
pprint.pprint(features) # noqa: T203
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,93 @@
# Copyright (C) 2023 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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.
from typing import Iterator
import capa.features.extractors.ghidra.file
import capa.features.extractors.ghidra.insn
import capa.features.extractors.ghidra.global_
import capa.features.extractors.ghidra.function
import capa.features.extractors.ghidra.basicblock
from capa.features.common import Feature
from capa.features.address import Address, AbsoluteVirtualAddress
from capa.features.extractors.base_extractor import (
BBHandle,
InsnHandle,
SampleHashes,
FunctionHandle,
StaticFeatureExtractor,
)
class GhidraFeatureExtractor(StaticFeatureExtractor):
def __init__(self):
import capa.features.extractors.ghidra.helpers as ghidra_helpers
super().__init__(
SampleHashes(
md5=capa.ghidra.helpers.get_file_md5(),
# ghidra doesn't expose this hash.
# https://ghidra.re/ghidra_docs/api/ghidra/program/model/listing/Program.html
#
# the hashes are stored in the database, not computed on the fly,
# so it's probably not trivial to add SHA1.
sha1="",
sha256=capa.ghidra.helpers.get_file_sha256(),
)
)
self.global_features: list[tuple[Feature, Address]] = []
self.global_features.extend(capa.features.extractors.ghidra.file.extract_file_format())
self.global_features.extend(capa.features.extractors.ghidra.global_.extract_os())
self.global_features.extend(capa.features.extractors.ghidra.global_.extract_arch())
self.imports = ghidra_helpers.get_file_imports()
self.externs = ghidra_helpers.get_file_externs()
self.fakes = ghidra_helpers.map_fake_import_addrs()
def get_base_address(self):
return AbsoluteVirtualAddress(currentProgram().getImageBase().getOffset()) # type: ignore [name-defined] # noqa: F821
def extract_global_features(self):
yield from self.global_features
def extract_file_features(self):
yield from capa.features.extractors.ghidra.file.extract_features()
def get_functions(self) -> Iterator[FunctionHandle]:
import capa.features.extractors.ghidra.helpers as ghidra_helpers
for fhandle in ghidra_helpers.get_function_symbols():
fh: FunctionHandle = FunctionHandle(
address=AbsoluteVirtualAddress(fhandle.getEntryPoint().getOffset()),
inner=fhandle,
ctx={"imports_cache": self.imports, "externs_cache": self.externs, "fakes_cache": self.fakes},
)
yield fh
@staticmethod
def get_function(addr: int) -> FunctionHandle:
func = getFunctionContaining(toAddr(addr)) # type: ignore [name-defined] # noqa: F821
return FunctionHandle(address=AbsoluteVirtualAddress(func.getEntryPoint().getOffset()), inner=func)
def extract_function_features(self, fh: FunctionHandle) -> Iterator[tuple[Feature, Address]]:
yield from capa.features.extractors.ghidra.function.extract_features(fh)
def get_basic_blocks(self, fh: FunctionHandle) -> Iterator[BBHandle]:
import capa.features.extractors.ghidra.helpers as ghidra_helpers
yield from ghidra_helpers.get_function_blocks(fh)
def extract_basic_block_features(self, fh: FunctionHandle, bbh: BBHandle) -> Iterator[tuple[Feature, Address]]:
yield from capa.features.extractors.ghidra.basicblock.extract_features(fh, bbh)
def get_instructions(self, fh: FunctionHandle, bbh: BBHandle) -> Iterator[InsnHandle]:
import capa.features.extractors.ghidra.helpers as ghidra_helpers
yield from ghidra_helpers.get_insn_in_range(bbh)
def extract_insn_features(self, fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle):
yield from capa.features.extractors.ghidra.insn.extract_features(fh, bbh, ih)

View File

@@ -0,0 +1,204 @@
# Copyright (C) 2023 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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.
import re
import struct
from typing import Iterator
from ghidra.program.model.symbol import SourceType, SymbolType
import capa.features.extractors.common
import capa.features.extractors.helpers
import capa.features.extractors.strings
import capa.features.extractors.ghidra.helpers
from capa.features.file import Export, Import, Section, FunctionName
from capa.features.common import FORMAT_PE, FORMAT_ELF, Format, String, Feature, Characteristic
from capa.features.address import NO_ADDRESS, Address, FileOffsetAddress, AbsoluteVirtualAddress
MAX_OFFSET_PE_AFTER_MZ = 0x200
def find_embedded_pe(block_bytez: bytes, mz_xor: list[tuple[bytes, bytes, int]]) -> Iterator[tuple[int, int]]:
"""check segment for embedded PE
adapted for Ghidra from:
https://github.com/vivisect/vivisect/blob/91e8419a861f4977https://github.com/vivisect/vivisect/blob/91e8419a861f49779f18316f155311967e696836/PE/carve.py#L259f18316f155311967e696836/PE/carve.py#L25
"""
todo = []
for mzx, pex, i in mz_xor:
for match in re.finditer(re.escape(mzx), block_bytez):
todo.append((match.start(), mzx, pex, i))
seg_max = len(block_bytez) # noqa: F821
while len(todo):
off, mzx, pex, i = todo.pop()
# MZ header has one field we will check e_lfanew is at 0x3c
e_lfanew = off + 0x3C
if seg_max < e_lfanew + 4:
continue
e_lfanew_bytes = block_bytez[e_lfanew : e_lfanew + 4]
newoff = struct.unpack("<I", capa.features.extractors.helpers.xor_static(e_lfanew_bytes, i))[0]
# assume XOR'd "PE" bytes exist within threshold
if newoff > MAX_OFFSET_PE_AFTER_MZ:
continue
peoff = off + newoff
if seg_max < peoff + 2:
continue
pe_bytes = block_bytez[peoff : peoff + 2]
if pe_bytes == pex:
yield off, i
def extract_file_embedded_pe() -> Iterator[tuple[Feature, Address]]:
"""extract embedded PE features"""
# pre-compute XOR pairs
mz_xor: list[tuple[bytes, bytes, int]] = [
(
capa.features.extractors.helpers.xor_static(b"MZ", i),
capa.features.extractors.helpers.xor_static(b"PE", i),
i,
)
for i in range(256)
]
for block in currentProgram().getMemory().getBlocks(): # type: ignore [name-defined] # noqa: F821
if not all((block.isLoaded(), block.isInitialized(), "Headers" not in block.getName())):
continue
for off, _ in find_embedded_pe(capa.features.extractors.ghidra.helpers.get_block_bytes(block), mz_xor):
# add offset back to block start
ea: int = block.getStart().add(off).getOffset()
yield Characteristic("embedded pe"), FileOffsetAddress(ea)
def extract_file_export_names() -> Iterator[tuple[Feature, Address]]:
"""extract function exports"""
st = currentProgram().getSymbolTable() # type: ignore [name-defined] # noqa: F821
for addr in st.getExternalEntryPointIterator():
yield Export(st.getPrimarySymbol(addr).getName()), AbsoluteVirtualAddress(addr.getOffset())
def extract_file_import_names() -> Iterator[tuple[Feature, Address]]:
"""extract function imports
1. imports by ordinal:
- modulename.#ordinal
2. imports by name, results in two features to support importname-only
matching:
- modulename.importname
- importname
"""
for f in currentProgram().getFunctionManager().getExternalFunctions(): # type: ignore [name-defined] # noqa: F821
for r in f.getSymbol().getReferences():
if r.getReferenceType().isData():
addr = r.getFromAddress().getOffset() # gets pointer to fake external addr
fstr = f.toString().split("::") # format: MODULE.dll::import / MODULE::Ordinal_*
if "Ordinal_" in fstr[1]:
fstr[1] = f"#{fstr[1].split('_')[1]}"
for name in capa.features.extractors.helpers.generate_symbols(fstr[0][:-4], fstr[1], include_dll=True):
yield Import(name), AbsoluteVirtualAddress(addr)
def extract_file_section_names() -> Iterator[tuple[Feature, Address]]:
"""extract section names"""
for block in currentProgram().getMemory().getBlocks(): # type: ignore [name-defined] # noqa: F821
yield Section(block.getName()), AbsoluteVirtualAddress(block.getStart().getOffset())
def extract_file_strings() -> Iterator[tuple[Feature, Address]]:
"""extract ASCII and UTF-16 LE strings"""
for block in currentProgram().getMemory().getBlocks(): # type: ignore [name-defined] # noqa: F821
if not block.isInitialized():
continue
p_bytes = capa.features.extractors.ghidra.helpers.get_block_bytes(block)
for s in capa.features.extractors.strings.extract_ascii_strings(p_bytes):
offset = block.getStart().getOffset() + s.offset
yield String(s.s), FileOffsetAddress(offset)
for s in capa.features.extractors.strings.extract_unicode_strings(p_bytes):
offset = block.getStart().getOffset() + s.offset
yield String(s.s), FileOffsetAddress(offset)
def extract_file_function_names() -> Iterator[tuple[Feature, Address]]:
"""
extract the names of statically-linked library functions.
"""
for sym in currentProgram().getSymbolTable().getAllSymbols(True): # type: ignore [name-defined] # noqa: F821
# .isExternal() misses more than this config for the function symbols
if sym.getSymbolType() == SymbolType.FUNCTION and sym.getSource() == SourceType.ANALYSIS and sym.isGlobal():
name = sym.getName() # starts to resolve names based on Ghidra's FidDB
if name.startswith("FID_conflict:"): # format: FID_conflict:<function-name>
name = name[13:]
addr = AbsoluteVirtualAddress(sym.getAddress().getOffset())
yield FunctionName(name), addr
if name.startswith("_"):
# some linkers may prefix linked routines with a `_` to avoid name collisions.
# extract features for both the mangled and un-mangled representations.
# e.g. `_fwrite` -> `fwrite`
# see: https://stackoverflow.com/a/2628384/87207
yield FunctionName(name[1:]), addr
def extract_file_format() -> Iterator[tuple[Feature, Address]]:
ef = currentProgram().getExecutableFormat() # type: ignore [name-defined] # noqa: F821
if "PE" in ef:
yield Format(FORMAT_PE), NO_ADDRESS
elif "ELF" in ef:
yield Format(FORMAT_ELF), NO_ADDRESS
elif "Raw" in ef:
# no file type to return when processing a binary file, but we want to continue processing
return
else:
raise NotImplementedError(f"unexpected file format: {ef}")
def extract_features() -> Iterator[tuple[Feature, Address]]:
"""extract file features"""
for file_handler in FILE_HANDLERS:
for feature, addr in file_handler():
yield feature, addr
FILE_HANDLERS = (
extract_file_embedded_pe,
extract_file_export_names,
extract_file_import_names,
extract_file_section_names,
extract_file_strings,
extract_file_function_names,
extract_file_format,
)
def main():
""" """
import pprint
pprint.pprint(list(extract_features())) # noqa: T203
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,73 @@
# Copyright (C) 2023 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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.
from typing import Iterator
import ghidra
from ghidra.program.model.block import BasicBlockModel, SimpleBlockIterator
import capa.features.extractors.ghidra.helpers
from capa.features.common import Feature, Characteristic
from capa.features.address import Address, AbsoluteVirtualAddress
from capa.features.extractors import loops
from capa.features.extractors.base_extractor import FunctionHandle
def extract_function_calls_to(fh: FunctionHandle):
"""extract callers to a function"""
f: ghidra.program.database.function.FunctionDB = fh.inner
for ref in f.getSymbol().getReferences():
if ref.getReferenceType().isCall():
yield Characteristic("calls to"), AbsoluteVirtualAddress(ref.getFromAddress().getOffset())
def extract_function_loop(fh: FunctionHandle):
f: ghidra.program.database.function.FunctionDB = fh.inner
edges = []
for block in SimpleBlockIterator(BasicBlockModel(currentProgram()), f.getBody(), monitor()): # type: ignore [name-defined] # noqa: F821
dests = block.getDestinations(monitor()) # type: ignore [name-defined] # noqa: F821
s_addrs = block.getStartAddresses()
while dests.hasNext(): # For loop throws Python TypeError
for addr in s_addrs:
edges.append((addr.getOffset(), dests.next().getDestinationAddress().getOffset()))
if loops.has_loop(edges):
yield Characteristic("loop"), AbsoluteVirtualAddress(f.getEntryPoint().getOffset())
def extract_recursive_call(fh: FunctionHandle):
f: ghidra.program.database.function.FunctionDB = fh.inner
for func in f.getCalledFunctions(monitor()): # type: ignore [name-defined] # noqa: F821
if func.getEntryPoint().getOffset() == f.getEntryPoint().getOffset():
yield Characteristic("recursive call"), AbsoluteVirtualAddress(f.getEntryPoint().getOffset())
def extract_features(fh: FunctionHandle) -> Iterator[tuple[Feature, Address]]:
for func_handler in FUNCTION_HANDLERS:
for feature, addr in func_handler(fh):
yield feature, addr
FUNCTION_HANDLERS = (extract_function_calls_to, extract_function_loop, extract_recursive_call)
def main():
""" """
features = []
for fhandle in capa.features.extractors.ghidra.helpers.get_function_symbols():
features.extend(list(extract_features(fhandle)))
import pprint
pprint.pprint(features) # noqa: T203
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,67 @@
# Copyright (C) 2023 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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.
import logging
import contextlib
from typing import Iterator
import capa.ghidra.helpers
import capa.features.extractors.elf
import capa.features.extractors.ghidra.helpers
from capa.features.common import OS, ARCH_I386, ARCH_AMD64, OS_WINDOWS, Arch, Feature
from capa.features.address import NO_ADDRESS, Address
logger = logging.getLogger(__name__)
def extract_os() -> Iterator[tuple[Feature, Address]]:
format_name: str = currentProgram().getExecutableFormat() # type: ignore [name-defined] # noqa: F821
if "PE" in format_name:
yield OS(OS_WINDOWS), NO_ADDRESS
elif "ELF" in format_name:
with contextlib.closing(capa.ghidra.helpers.GHIDRAIO()) as f:
os = capa.features.extractors.elf.detect_elf_os(f)
yield OS(os), NO_ADDRESS
else:
# we likely end up here:
# 1. handling shellcode, or
# 2. handling a new file format (e.g. macho)
#
# for (1) we can't do much - its shellcode and all bets are off.
# we could maybe accept a further CLI argument to specify the OS,
# but i think this would be rarely used.
# rules that rely on OS conditions will fail to match on shellcode.
#
# for (2), this logic will need to be updated as the format is implemented.
logger.debug("unsupported file format: %s, will not guess OS", format_name)
return
def extract_arch() -> Iterator[tuple[Feature, Address]]:
lang_id = currentProgram().getMetadata().get("Language ID") # type: ignore [name-defined] # noqa: F821
if "x86" in lang_id and "64" in lang_id:
yield Arch(ARCH_AMD64), NO_ADDRESS
elif "x86" in lang_id and "32" in lang_id:
yield Arch(ARCH_I386), NO_ADDRESS
elif "x86" not in lang_id:
logger.debug("unsupported architecture: non-32-bit nor non-64-bit intel")
return
else:
# we likely end up here:
# 1. handling a new architecture (e.g. aarch64)
#
# for (1), this logic will need to be updated as the format is implemented.
logger.debug("unsupported architecture: %s", lang_id)
return

View File

@@ -0,0 +1,301 @@
# Copyright (C) 2023 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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.
from typing import Iterator
import ghidra
import java.lang
from ghidra.program.model.lang import OperandType
from ghidra.program.model.block import BasicBlockModel, SimpleBlockIterator
from ghidra.program.model.symbol import SourceType, SymbolType
from ghidra.program.model.address import AddressSpace
import capa.features.extractors.helpers
from capa.features.common import THUNK_CHAIN_DEPTH_DELTA
from capa.features.address import AbsoluteVirtualAddress
from capa.features.extractors.base_extractor import BBHandle, InsnHandle, FunctionHandle
def ints_to_bytes(bytez: list[int]) -> bytes:
"""convert Java signed ints to Python bytes
args:
bytez: list of Java signed ints
"""
return bytes([b & 0xFF for b in bytez])
def find_byte_sequence(addr: ghidra.program.model.address.Address, seq: bytes) -> Iterator[int]:
"""yield all ea of a given byte sequence
args:
addr: start address
seq: bytes to search e.g. b"\x01\x03"
"""
seqstr = "".join([f"\\x{b:02x}" for b in seq])
eas = findBytes(addr, seqstr, java.lang.Integer.MAX_VALUE, 1) # type: ignore [name-defined] # noqa: F821
yield from eas
def get_bytes(addr: ghidra.program.model.address.Address, length: int) -> bytes:
"""yield length bytes at addr
args:
addr: Address to begin pull from
length: length of bytes to pull
"""
try:
return ints_to_bytes(getBytes(addr, length)) # type: ignore [name-defined] # noqa: F821
except RuntimeError:
return b""
def get_block_bytes(block: ghidra.program.model.mem.MemoryBlock) -> bytes:
"""yield all bytes in a given block
args:
block: MemoryBlock to pull from
"""
return get_bytes(block.getStart(), block.getSize())
def get_function_symbols():
"""yield all non-external function symbols"""
yield from currentProgram().getFunctionManager().getFunctionsNoStubs(True) # type: ignore [name-defined] # noqa: F821
def get_function_blocks(fh: FunctionHandle) -> Iterator[BBHandle]:
"""yield BBHandle for each bb in a given function"""
func: ghidra.program.database.function.FunctionDB = fh.inner
for bb in SimpleBlockIterator(BasicBlockModel(currentProgram()), func.getBody(), monitor()): # type: ignore [name-defined] # noqa: F821
yield BBHandle(address=AbsoluteVirtualAddress(bb.getMinAddress().getOffset()), inner=bb)
def get_insn_in_range(bbh: BBHandle) -> Iterator[InsnHandle]:
"""yield InshHandle for each insn in a given basicblock"""
for insn in currentProgram().getListing().getInstructions(bbh.inner, True): # type: ignore [name-defined] # noqa: F821
yield InsnHandle(address=AbsoluteVirtualAddress(insn.getAddress().getOffset()), inner=insn)
def get_file_imports() -> dict[int, list[str]]:
"""get all import names & addrs"""
import_dict: dict[int, list[str]] = {}
for f in currentProgram().getFunctionManager().getExternalFunctions(): # type: ignore [name-defined] # noqa: F821
for r in f.getSymbol().getReferences():
if r.getReferenceType().isData():
addr = r.getFromAddress().getOffset() # gets pointer to fake external addr
ex_loc = f.getExternalLocation().getAddress() # map external locations as well (offset into module files)
fstr = f.toString().split("::") # format: MODULE.dll::import / MODULE::Ordinal_* / <EXTERNAL>::import
if "Ordinal_" in fstr[1]:
fstr[1] = f"#{fstr[1].split('_')[1]}"
# <EXTERNAL> mostly shows up in ELF files, otherwise, strip '.dll' w/ [:-4]
fstr[0] = "*" if "<EXTERNAL>" in fstr[0] else fstr[0][:-4]
for name in capa.features.extractors.helpers.generate_symbols(fstr[0], fstr[1]):
import_dict.setdefault(addr, []).append(name)
if ex_loc:
import_dict.setdefault(ex_loc.getOffset(), []).append(name)
return import_dict
def get_file_externs() -> dict[int, list[str]]:
"""
Gets function names & addresses of statically-linked library functions
Ghidra's external namespace is mostly reserved for dynamically-linked
imports. Statically-linked functions are part of the global namespace.
Filtering on the type, source, and namespace of the symbols yield more
statically-linked library functions.
Example: (PMA Lab 16-01.exe_) 7faafc7e4a5c736ebfee6abbbc812d80:0x407490
- __aulldiv
- Note: See Symbol Table labels
"""
extern_dict: dict[int, list[str]] = {}
for sym in currentProgram().getSymbolTable().getAllSymbols(True): # type: ignore [name-defined] # noqa: F821
# .isExternal() misses more than this config for the function symbols
if sym.getSymbolType() == SymbolType.FUNCTION and sym.getSource() == SourceType.ANALYSIS and sym.isGlobal():
name = sym.getName() # starts to resolve names based on Ghidra's FidDB
if name.startswith("FID_conflict:"): # format: FID_conflict:<function-name>
name = name[13:]
extern_dict.setdefault(sym.getAddress().getOffset(), []).append(name)
if name.startswith("_"):
# some linkers may prefix linked routines with a `_` to avoid name collisions.
# extract features for both the mangled and un-mangled representations.
# e.g. `_fwrite` -> `fwrite`
# see: https://stackoverflow.com/a/2628384/87207
extern_dict.setdefault(sym.getAddress().getOffset(), []).append(name[1:])
return extern_dict
def map_fake_import_addrs() -> dict[int, list[int]]:
"""
Map ghidra's fake import entrypoints to their
real addresses
Helps as many Ghidra Scripting API calls end up returning
these external (fake) addresses.
Undocumented but intended Ghidra behavior:
- Import entryPoint fields are stored in the 'EXTERNAL:' AddressSpace.
'getEntryPoint()' returns the entryPoint field, which is an offset
from the beginning of the assigned AddressSpace. In the case of externals,
they start from 1 and increment.
https://github.com/NationalSecurityAgency/ghidra/blob/26d4bd9104809747c21f2528cab8aba9aef9acd5/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/function/ExternalFunctionDBTest.java#L90
Example: (mimikatz.exe_) 5f66b82558ca92e54e77f216ef4c066c:0x473090
- 0x473090 -> PTR_CreateServiceW_00473090
- 'EXTERNAL:00000025' -> External Address (ghidra.program.model.address.SpecialAddress)
"""
fake_dict: dict[int, list[int]] = {}
for f in currentProgram().getFunctionManager().getExternalFunctions(): # type: ignore [name-defined] # noqa: F821
for r in f.getSymbol().getReferences():
if r.getReferenceType().isData():
fake_dict.setdefault(f.getEntryPoint().getOffset(), []).append(r.getFromAddress().getOffset())
return fake_dict
def check_addr_for_api(
addr: ghidra.program.model.address.Address,
fakes: dict[int, list[int]],
imports: dict[int, list[str]],
externs: dict[int, list[str]],
) -> bool:
offset = addr.getOffset()
fake = fakes.get(offset)
if fake:
return True
imp = imports.get(offset)
if imp:
return True
extern = externs.get(offset)
if extern:
return True
return False
def is_call_or_jmp(insn: ghidra.program.database.code.InstructionDB) -> bool:
return any(mnem in insn.getMnemonicString() for mnem in ["CALL", "J"]) # JMP, JNE, JNZ, etc
def is_sp_modified(insn: ghidra.program.database.code.InstructionDB) -> bool:
for i in range(insn.getNumOperands()):
if insn.getOperandType(i) == OperandType.REGISTER:
return "SP" in insn.getRegister(i).getName() and insn.getOperandRefType(i).isWrite()
return False
def is_stack_referenced(insn: ghidra.program.database.code.InstructionDB) -> bool:
"""generic catch-all for stack references"""
for i in range(insn.getNumOperands()):
if insn.getOperandType(i) == OperandType.REGISTER:
if "BP" in insn.getRegister(i).getName():
return True
else:
continue
return any(ref.isStackReference() for ref in insn.getReferencesFrom())
def is_zxor(insn: ghidra.program.database.code.InstructionDB) -> bool:
# assume XOR insn
# XOR's against the same operand zero out
ops = []
operands = []
for i in range(insn.getNumOperands()):
ops.append(insn.getOpObjects(i))
# Operands stored in a 2D array
for j in range(len(ops)):
for k in range(len(ops[j])):
operands.append(ops[j][k])
return all(n == operands[0] for n in operands)
def handle_thunk(addr: ghidra.program.model.address.Address):
"""Follow thunk chains down to a reasonable depth"""
ref = addr
for _ in range(THUNK_CHAIN_DEPTH_DELTA):
thunk_jmp = getInstructionAt(ref) # type: ignore [name-defined] # noqa: F821
if thunk_jmp and is_call_or_jmp(thunk_jmp):
if OperandType.isAddress(thunk_jmp.getOperandType(0)):
ref = thunk_jmp.getAddress(0)
else:
thunk_dat = getDataContaining(ref) # type: ignore [name-defined] # noqa: F821
if thunk_dat and thunk_dat.isDefined() and thunk_dat.isPointer():
ref = thunk_dat.getValue()
break # end of thunk chain reached
return ref
def dereference_ptr(insn: ghidra.program.database.code.InstructionDB):
addr_code = OperandType.ADDRESS | OperandType.CODE
to_deref = insn.getAddress(0)
dat = getDataContaining(to_deref) # type: ignore [name-defined] # noqa: F821
if insn.getOperandType(0) == addr_code:
thfunc = getFunctionContaining(to_deref) # type: ignore [name-defined] # noqa: F821
if thfunc and thfunc.isThunk():
return handle_thunk(to_deref)
else:
# if it doesn't point to a thunk, it's usually a jmp to a label
return to_deref
if not dat:
return to_deref
if dat.isDefined() and dat.isPointer():
addr = dat.getValue()
# now we need to check the addr space to see if it is truly resolvable
# ghidra sometimes likes to hand us direct RAM addrs, which typically point
# to api calls that we can't actually resolve as such
if addr.getAddressSpace().getType() == AddressSpace.TYPE_RAM:
return to_deref
else:
return addr
else:
return to_deref
def find_data_references_from_insn(insn, max_depth: int = 10):
"""yield data references from given instruction"""
for reference in insn.getReferencesFrom():
if not reference.getReferenceType().isData():
# only care about data references
continue
to_addr = reference.getToAddress()
for _ in range(max_depth - 1):
data = getDataAt(to_addr) # type: ignore [name-defined] # noqa: F821
if data and data.isPointer():
ptr_value = data.getValue()
if ptr_value is None:
break
to_addr = ptr_value
else:
break
yield to_addr

View File

@@ -0,0 +1,503 @@
# Copyright (C) 2023 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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.
from typing import Any, Iterator
import ghidra
from ghidra.program.model.lang import OperandType
from ghidra.program.model.block import SimpleBlockModel
import capa.features.extractors.helpers
import capa.features.extractors.ghidra.helpers
from capa.features.insn import API, MAX_STRUCTURE_SIZE, Number, Offset, Mnemonic, OperandNumber, OperandOffset
from capa.features.common import MAX_BYTES_FEATURE_SIZE, Bytes, String, Feature, Characteristic
from capa.features.address import Address, AbsoluteVirtualAddress
from capa.features.extractors.base_extractor import BBHandle, InsnHandle, FunctionHandle
# security cookie checks may perform non-zeroing XORs, these are expected within a certain
# byte range within the first and returning basic blocks, this helps to reduce FP features
SECURITY_COOKIE_BYTES_DELTA = 0x40
OPERAND_TYPE_DYNAMIC_ADDRESS = OperandType.DYNAMIC | OperandType.ADDRESS
def get_imports(ctx: dict[str, Any]) -> dict[int, Any]:
"""Populate the import cache for this context"""
if "imports_cache" not in ctx:
ctx["imports_cache"] = capa.features.extractors.ghidra.helpers.get_file_imports()
return ctx["imports_cache"]
def get_externs(ctx: dict[str, Any]) -> dict[int, Any]:
"""Populate the externs cache for this context"""
if "externs_cache" not in ctx:
ctx["externs_cache"] = capa.features.extractors.ghidra.helpers.get_file_externs()
return ctx["externs_cache"]
def get_fakes(ctx: dict[str, Any]) -> dict[int, Any]:
"""Populate the fake import addrs cache for this context"""
if "fakes_cache" not in ctx:
ctx["fakes_cache"] = capa.features.extractors.ghidra.helpers.map_fake_import_addrs()
return ctx["fakes_cache"]
def check_for_api_call(
insn, externs: dict[int, Any], fakes: dict[int, Any], imports: dict[int, Any], imp_or_ex: bool
) -> Iterator[Any]:
"""check instruction for API call
params:
externs - external library functions cache
fakes - mapped fake import addresses cache
imports - imported functions cache
imp_or_ex - flag to check imports or externs
yields:
matched api calls
"""
info = ()
funcs = imports if imp_or_ex else externs
# assume only CALLs or JMPs are passed
ref_type = insn.getOperandType(0)
addr_data = OperandType.ADDRESS | OperandType.DATA # needs dereferencing
addr_code = OperandType.ADDRESS | OperandType.CODE # needs dereferencing
if OperandType.isRegister(ref_type):
if OperandType.isAddress(ref_type):
# If it's an address in a register, check the mapped fake addrs
# since they're dereferenced to their fake addrs
op_ref = insn.getAddress(0).getOffset()
ref = fakes.get(op_ref) # obtain the real addr
if not ref:
return
else:
return
elif ref_type in (addr_data, addr_code) or (OperandType.isIndirect(ref_type) and OperandType.isAddress(ref_type)):
# we must dereference and check if the addr is a pointer to an api function
addr_ref = capa.features.extractors.ghidra.helpers.dereference_ptr(insn)
if not capa.features.extractors.ghidra.helpers.check_addr_for_api(addr_ref, fakes, imports, externs):
return
ref = addr_ref.getOffset()
elif ref_type == OPERAND_TYPE_DYNAMIC_ADDRESS or ref_type == OperandType.DYNAMIC:
return # cannot resolve dynamics statically
else:
# pure address does not need to get dereferenced/ handled
addr_ref = insn.getAddress(0)
if not addr_ref:
# If it returned null, it was an indirect
# that had no address reference.
# This check is faster than checking for (indirect and not address)
return
if not capa.features.extractors.ghidra.helpers.check_addr_for_api(addr_ref, fakes, imports, externs):
return
ref = addr_ref.getOffset()
if isinstance(ref, list): # ref from REG | ADDR
for r in ref:
info = funcs.get(r) # type: ignore
if info:
yield info
else:
info = funcs.get(ref) # type: ignore
if info:
yield info
def extract_insn_api_features(fh: FunctionHandle, bb: BBHandle, ih: InsnHandle) -> Iterator[tuple[Feature, Address]]:
insn: ghidra.program.database.code.InstructionDB = ih.inner
if not capa.features.extractors.ghidra.helpers.is_call_or_jmp(insn):
return
externs = get_externs(fh.ctx)
fakes = get_fakes(fh.ctx)
imports = get_imports(fh.ctx)
# check calls to imported functions
for api in check_for_api_call(insn, externs, fakes, imports, True):
for imp in api:
yield API(imp), ih.address
# check calls to extern functions
for api in check_for_api_call(insn, externs, fakes, imports, False):
for ext in api:
yield API(ext), ih.address
def extract_insn_number_features(fh: FunctionHandle, bb: BBHandle, ih: InsnHandle) -> Iterator[tuple[Feature, Address]]:
"""
parse instruction number features
example:
push 3136B0h ; dwControlCode
"""
insn: ghidra.program.database.code.InstructionDB = ih.inner
if insn.getMnemonicString().startswith("RET"):
# skip things like:
# .text:0042250E retn 8
return
if capa.features.extractors.ghidra.helpers.is_sp_modified(insn):
# skip things like:
# .text:00401145 add esp, 0Ch
return
for i in range(insn.getNumOperands()):
# Exceptions for LEA insn:
# invalid operand encoding, considered numbers instead of offsets
# see: mimikatz.exe_:0x4018C0
if insn.getOperandType(i) == OperandType.DYNAMIC and insn.getMnemonicString().startswith("LEA"):
# Additional check, avoid yielding "wide" values (ex. mimikatz.exe:0x471EE6 LEA EBX, [ECX + EAX*0x4])
op_objs = insn.getOpObjects(i)
if len(op_objs) == 3: # ECX, EAX, 0x4
continue
if isinstance(op_objs[-1], ghidra.program.model.scalar.Scalar):
const = op_objs[-1].getUnsignedValue()
addr = ih.address
yield Number(const), addr
yield OperandNumber(i, const), addr
elif not OperandType.isScalar(insn.getOperandType(i)):
# skip things like:
# references, void types
continue
else:
const = insn.getScalar(i).getUnsignedValue()
addr = ih.address
yield Number(const), addr
yield OperandNumber(i, const), addr
if insn.getMnemonicString().startswith("ADD") and 0 < const < MAX_STRUCTURE_SIZE:
# for pattern like:
#
# add eax, 0x10
#
# assume 0x10 is also an offset (imagine eax is a pointer).
yield Offset(const), addr
yield OperandOffset(i, const), addr
def extract_insn_offset_features(fh: FunctionHandle, bb: BBHandle, ih: InsnHandle) -> Iterator[tuple[Feature, Address]]:
"""
parse instruction structure offset features
example:
.text:0040112F cmp [esi+4], ebx
"""
insn: ghidra.program.database.code.InstructionDB = ih.inner
if insn.getMnemonicString().startswith("LEA"):
return
if capa.features.extractors.ghidra.helpers.is_stack_referenced(insn):
# ignore stack references
return
# Ghidra stores operands in 2D arrays if they contain offsets
for i in range(insn.getNumOperands()):
if insn.getOperandType(i) == OperandType.DYNAMIC: # e.g. [esi + 4]
# manual extraction, since the default api calls only work on the 1st dimension of the array
op_objs = insn.getOpObjects(i)
if not op_objs:
continue
if isinstance(op_objs[-1], ghidra.program.model.scalar.Scalar):
op_off = op_objs[-1].getValue()
else:
op_off = 0
yield Offset(op_off), ih.address
yield OperandOffset(i, op_off), ih.address
def extract_insn_bytes_features(fh: FunctionHandle, bb: BBHandle, ih: InsnHandle) -> Iterator[tuple[Feature, Address]]:
"""
parse referenced byte sequences
example:
push offset iid_004118d4_IShellLinkA ; riid
"""
for addr in capa.features.extractors.ghidra.helpers.find_data_references_from_insn(ih.inner):
data = getDataAt(addr) # type: ignore [name-defined] # noqa: F821
if data and not data.hasStringValue():
extracted_bytes = capa.features.extractors.ghidra.helpers.get_bytes(addr, MAX_BYTES_FEATURE_SIZE)
if extracted_bytes and not capa.features.extractors.helpers.all_zeros(extracted_bytes):
yield Bytes(extracted_bytes), ih.address
def extract_insn_string_features(fh: FunctionHandle, bb: BBHandle, ih: InsnHandle) -> Iterator[tuple[Feature, Address]]:
"""
parse instruction string features
example:
push offset aAcr ; "ACR > "
"""
for addr in capa.features.extractors.ghidra.helpers.find_data_references_from_insn(ih.inner):
data = getDataAt(addr) # type: ignore [name-defined] # noqa: F821
if data and data.hasStringValue():
yield String(data.getValue()), ih.address
def extract_insn_mnemonic_features(
fh: FunctionHandle, bb: BBHandle, ih: InsnHandle
) -> Iterator[tuple[Feature, Address]]:
"""parse instruction mnemonic features"""
insn: ghidra.program.database.code.InstructionDB = ih.inner
yield Mnemonic(insn.getMnemonicString().lower()), ih.address
def extract_insn_obfs_call_plus_5_characteristic_features(
fh: FunctionHandle, bb: BBHandle, ih: InsnHandle
) -> Iterator[tuple[Feature, Address]]:
"""
parse call $+5 instruction from the given instruction.
"""
insn: ghidra.program.database.code.InstructionDB = ih.inner
if not capa.features.extractors.ghidra.helpers.is_call_or_jmp(insn):
return
code_ref = OperandType.ADDRESS | OperandType.CODE
ref = insn.getAddress()
for i in range(insn.getNumOperands()):
if insn.getOperandType(i) == code_ref:
ref = insn.getAddress(i)
if insn.getAddress().add(5) == ref:
yield Characteristic("call $+5"), ih.address
def extract_insn_segment_access_features(
fh: FunctionHandle, bb: BBHandle, ih: InsnHandle
) -> Iterator[tuple[Feature, Address]]:
"""parse instruction fs or gs access"""
insn: ghidra.program.database.code.InstructionDB = ih.inner
insn_str = insn.toString()
if "FS:" in insn_str:
yield Characteristic("fs access"), ih.address
if "GS:" in insn_str:
yield Characteristic("gs access"), ih.address
def extract_insn_peb_access_characteristic_features(
fh: FunctionHandle, bb: BBHandle, ih: InsnHandle
) -> Iterator[tuple[Feature, Address]]:
"""parse instruction peb access
fs:[0x30] on x86, gs:[0x60] on x64
"""
insn: ghidra.program.database.code.InstructionDB = ih.inner
insn_str = insn.toString()
if insn_str.startswith(("PUSH", "MOV")):
if "FS:[0x30]" in insn_str or "GS:[0x60]" in insn_str:
yield Characteristic("peb access"), ih.address
def extract_insn_cross_section_cflow(
fh: FunctionHandle, bb: BBHandle, ih: InsnHandle
) -> Iterator[tuple[Feature, Address]]:
"""inspect the instruction for a CALL or JMP that crosses section boundaries"""
insn: ghidra.program.database.code.InstructionDB = ih.inner
if not capa.features.extractors.ghidra.helpers.is_call_or_jmp(insn):
return
externs = get_externs(fh.ctx)
fakes = get_fakes(fh.ctx)
imports = get_imports(fh.ctx)
# OperandType to dereference
addr_data = OperandType.ADDRESS | OperandType.DATA
addr_code = OperandType.ADDRESS | OperandType.CODE
ref_type = insn.getOperandType(0)
# both OperandType flags must be present
# bail on REGISTER alone
if OperandType.isRegister(ref_type):
if OperandType.isAddress(ref_type):
ref = insn.getAddress(0) # Ghidra dereferences REG | ADDR
if capa.features.extractors.ghidra.helpers.check_addr_for_api(ref, fakes, imports, externs):
return
else:
return
elif ref_type in (addr_data, addr_code) or (OperandType.isIndirect(ref_type) and OperandType.isAddress(ref_type)):
# we must dereference and check if the addr is a pointer to an api function
ref = capa.features.extractors.ghidra.helpers.dereference_ptr(insn)
if capa.features.extractors.ghidra.helpers.check_addr_for_api(ref, fakes, imports, externs):
return
elif ref_type == OPERAND_TYPE_DYNAMIC_ADDRESS or ref_type == OperandType.DYNAMIC:
return # cannot resolve dynamics statically
else:
# pure address does not need to get dereferenced/ handled
ref = insn.getAddress(0)
if not ref:
# If it returned null, it was an indirect
# that had no address reference.
# This check is faster than checking for (indirect and not address)
return
if capa.features.extractors.ghidra.helpers.check_addr_for_api(ref, fakes, imports, externs):
return
this_mem_block = getMemoryBlock(insn.getAddress()) # type: ignore [name-defined] # noqa: F821
ref_block = getMemoryBlock(ref) # type: ignore [name-defined] # noqa: F821
if ref_block != this_mem_block:
yield Characteristic("cross section flow"), ih.address
def extract_function_calls_from(
fh: FunctionHandle,
bb: BBHandle,
ih: InsnHandle,
) -> Iterator[tuple[Feature, Address]]:
"""extract functions calls from features
most relevant at the function scope, however, its most efficient to extract at the instruction scope
"""
insn: ghidra.program.database.code.InstructionDB = ih.inner
if insn.getMnemonicString().startswith("CALL"):
# This method of "dereferencing" addresses/ pointers
# is not as robust as methods in other functions,
# but works just fine for this one
reference = 0
for ref in insn.getReferencesFrom():
addr = ref.getToAddress()
# avoid returning fake addrs
if not addr.isExternalAddress():
reference = addr.getOffset()
# if a reference is < 0, then ghidra pulled an offset from a DYNAMIC | ADDR (usually a stackvar)
# these cannot be resolved to actual addrs
if reference > 0:
yield Characteristic("calls from"), AbsoluteVirtualAddress(reference)
def extract_function_indirect_call_characteristic_features(
fh: FunctionHandle,
bb: BBHandle,
ih: InsnHandle,
) -> Iterator[tuple[Feature, Address]]:
"""extract indirect function calls (e.g., call eax or call dword ptr [edx+4])
does not include calls like => call ds:dword_ABD4974
most relevant at the function or basic block scope;
however, its most efficient to extract at the instruction scope
"""
insn: ghidra.program.database.code.InstructionDB = ih.inner
if insn.getMnemonicString().startswith("CALL"):
if OperandType.isRegister(insn.getOperandType(0)):
yield Characteristic("indirect call"), ih.address
if OperandType.isIndirect(insn.getOperandType(0)):
yield Characteristic("indirect call"), ih.address
def check_nzxor_security_cookie_delta(
fh: ghidra.program.database.function.FunctionDB, insn: ghidra.program.database.code.InstructionDB
):
"""Get the function containing the insn
Get the last block of the function that contains the insn
Check the bb containing the insn
Check the last bb of the function containing the insn
"""
model = SimpleBlockModel(currentProgram()) # type: ignore [name-defined] # noqa: F821
insn_addr = insn.getAddress()
func_asv = fh.getBody()
first_addr = func_asv.getMinAddress()
last_addr = func_asv.getMaxAddress()
if model.getFirstCodeBlockContaining(
first_addr, monitor() # type: ignore [name-defined] # noqa: F821
) == model.getFirstCodeBlockContaining(
last_addr, monitor() # type: ignore [name-defined] # noqa: F821
):
if insn_addr < first_addr.add(SECURITY_COOKIE_BYTES_DELTA):
return True
else:
return insn_addr > last_addr.add(SECURITY_COOKIE_BYTES_DELTA * -1)
else:
return False
def extract_insn_nzxor_characteristic_features(
fh: FunctionHandle,
bb: BBHandle,
ih: InsnHandle,
) -> Iterator[tuple[Feature, Address]]:
f: ghidra.program.database.function.FunctionDB = fh.inner
insn: ghidra.program.database.code.InstructionDB = ih.inner
if "XOR" not in insn.getMnemonicString():
return
if capa.features.extractors.ghidra.helpers.is_stack_referenced(insn):
return
if capa.features.extractors.ghidra.helpers.is_zxor(insn):
return
if check_nzxor_security_cookie_delta(f, insn):
return
yield Characteristic("nzxor"), ih.address
def extract_features(
fh: FunctionHandle,
bb: BBHandle,
insn: InsnHandle,
) -> Iterator[tuple[Feature, Address]]:
for insn_handler in INSTRUCTION_HANDLERS:
for feature, addr in insn_handler(fh, bb, insn):
yield feature, addr
INSTRUCTION_HANDLERS = (
extract_insn_api_features,
extract_insn_number_features,
extract_insn_bytes_features,
extract_insn_string_features,
extract_insn_offset_features,
extract_insn_nzxor_characteristic_features,
extract_insn_mnemonic_features,
extract_insn_obfs_call_plus_5_characteristic_features,
extract_insn_peb_access_characteristic_features,
extract_insn_cross_section_cflow,
extract_insn_segment_access_features,
extract_function_calls_from,
extract_function_indirect_call_characteristic_features,
)
def main():
""" """
features = []
from capa.features.extractors.ghidra.extractor import GhidraFeatureExtractor
for fh in GhidraFeatureExtractor().get_functions():
for bb in capa.features.extractors.ghidra.helpers.get_function_blocks(fh):
for insn in capa.features.extractors.ghidra.helpers.get_insn_in_range(bb):
features.extend(list(extract_features(fh, bb, insn)))
import pprint
pprint.pprint(features) # noqa: T203
if __name__ == "__main__":
main()

Some files were not shown because too many files have changed in this diff Show More