Compare commits

...

1659 Commits

Author SHA1 Message Date
mr-tz
481ae685e1 move sigs to capa directory 2024-01-18 12:31:55 +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
Willi Ballenthin
781c33d13c Merge pull request #1652 from mandiant/williballenthin-patch-1
v6.0.0
2023-07-18 18:26:52 +02:00
Willi Ballenthin
70a1e66020 ci: publish: remove dev code 2023-07-18 14:02:35 +00:00
Willi Ballenthin
91b65d1d7f ci: publish: remove old commented code 2023-07-18 14:01:58 +00:00
Willi Ballenthin
a22dd65032 Merge branch 'master' into williballenthin-patch-1 2023-07-18 16:00:47 +02:00
Willi Ballenthin
3899662cbd v6.0.0 2023-07-18 14:00:09 +00:00
Willi Ballenthin
b73e1e3d7f pyproject: set readme context type 2023-07-18 13:56:04 +00:00
Willi Ballenthin
25624a1b46 ci: publish: dev release 2023-07-18 13:38:05 +00:00
Willi Ballenthin
e3c8cb74df ci: publish: dev release 2023-07-18 13:33:01 +00:00
Willi Ballenthin
f99824d996 v6.0.0a4 2023-07-18 13:22:11 +00:00
Willi Ballenthin
33cb81449c ci: publish: try to fix perm errors 2023-07-18 13:21:47 +00:00
Willi Ballenthin
c49385e681 Merge pull request #1651 from mandiant/williballenthin-patch-1
v6.0.0a3
2023-07-18 14:33:05 +02:00
Willi Ballenthin
5277f3b640 v6.0.0a3 2023-07-18 12:23:25 +00:00
Willi Ballenthin
dbfcbaa98e ci: publish: fix file name globbing 2023-07-18 12:23:15 +00:00
Willi Ballenthin
a2d70a12a9 Merge pull request #1650 from mandiant/williballenthin-patch-1
v6.0.0a2
2023-07-18 14:15:55 +02:00
Willi Ballenthin
be58f65ae5 v6.0.0a2 2023-07-18 11:37:45 +00:00
Willi Ballenthin
15caa9ee6e ci: publish: remove incorrect name 2023-07-18 13:35:24 +02:00
Willi Ballenthin
0398baa752 Merge pull request #1648 from mandiant/fix/issue-1622
prep v6.0.0a1
2023-07-18 13:30:43 +02:00
Willi Ballenthin
b1214df621 Merge branch 'master' into fix/issue-1622 2023-07-18 13:30:32 +02:00
Willi Ballenthin
c0ed955362 Merge pull request #1647 from mandiant/williballenthin-patch-1
contributing: document CLA
2023-07-18 12:53:48 +02:00
Yacine Elhamer
bc46bf3202 add vverbose rendering 2023-07-18 11:26:20 +01:00
Willi Ballenthin
1c6434a380 changelog: remove old formatting 2023-07-18 10:10:36 +00:00
Willi Ballenthin
fff1248ec4 changelog: fix links 2023-07-18 10:07:18 +00:00
Willi Ballenthin
14f0589194 v6.0.0a1 2023-07-18 10:04:39 +00:00
Willi Ballenthin
d47703fada v6.0 changelog 2023-07-18 10:02:07 +00:00
Willi Ballenthin
faf3ca53f7 changelog 2023-07-18 09:21:51 +00:00
Willi Ballenthin
18e0408577 contributing: document CLA 2023-07-18 11:18:28 +02:00
Willi Ballenthin
972fbe7290 Merge pull request #1641 from mandiant/fix/issue-1624
forwarded export features
2023-07-18 10:55:30 +02:00
Willi Ballenthin
40793eeefb tests: bn: update link to tracking issue 2023-07-17 18:07:25 +02:00
Willi Ballenthin
221a5a9f03 tests: xfail binja forwarded exports 2023-07-17 17:56:33 +02:00
Willi Ballenthin
d1f5a6e76b Merge branch 'fix/issue-1624' of personal.github.com:mandiant/capa into fix/issue-1624 2023-07-17 17:35:47 +02:00
Willi Ballenthin
d2567692a8 factor out common forwarded export name normalization 2023-07-17 17:32:40 +02: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
Willi Ballenthin
7c67fae52a changelog: formatting 2023-07-13 16:53:35 +02:00
Willi Ballenthin
ebae5e5ca0 Merge branch 'master' into fix/issue-1624 2023-07-13 16:51:41 +02:00
Capa Bot
244d56e32a Sync capa-testfiles submodule 2023-07-13 14:50:40 +00:00
Willi Ballenthin
5f2b92de40 Merge branch 'master' into fix/issue-1624 2023-07-13 16:50:35 +02:00
Capa Bot
1065ff9779 Sync capa-testfiles submodule 2023-07-13 14:49:40 +00:00
Willi Ballenthin
5253ad7014 Merge pull request #1640 from mandiant/fix/issue-1592
tests: make fixtures available via conftest.py
2023-07-13 15:39:11 +02:00
Willi Ballenthin
82223dcdc9 conftest: isort 2023-07-13 13:12:13 +00:00
Willi Ballenthin
724f9e4b81 conftest: isort 2023-07-13 14:52:05 +02:00
Willi Ballenthin
c4da4bcfe7 conftest: update noqa ignores 2023-07-13 14:35:09 +02:00
Willi Ballenthin
fd36946c4b conftest: import symbols prefixed with _ 2023-07-13 14:32:24 +02:00
Willi Ballenthin
8c9853ad12 Merge pull request #1639 from mandiant/fix/issue-1636
main: don't show spinner when debug messages are emitted
2023-07-13 13:47:55 +02:00
Willi Ballenthin
562a61930d Merge pull request #1635 from mandiant/feat/ci-toplevel-permissions
ci: set top level permissions to satisfy code scanning
2023-07-13 13:20:06 +02:00
Willi Ballenthin
f9d210367e Merge pull request #1638 from mandiant/feat/issue-1290
main: log time taken to analyze each function
2023-07-13 13:19:53 +02:00
Willi Ballenthin
bb6557ea0a ida: extract forwarded export features 2023-07-13 12:18:57 +02:00
Willi Ballenthin
cb8133467b Merge branch 'fix/issue-1624' of personal.github.com:mandiant/capa into fix/issue-1624 2023-07-13 11:55:56 +02:00
Willi Ballenthin
718813bc1c Merge branch 'master' into fix/issue-1624 2023-07-13 16:16:40 +02:00
Willi Ballenthin
394c3807c1 Merge branch 'master' into fix/issue-1624 2023-07-13 11:55:46 +02:00
Willi Ballenthin
74924990a2 changelog 2023-07-13 11:50:56 +02:00
Willi Ballenthin
330f2a6b9b viv: emit forwarded export features
ref #1592
2023-07-13 11:47:32 +02:00
Willi Ballenthin
6b81c77d22 profile-time: workaround for flake8-encodings bug
https://github.com/python-formate/flake8-encodings/issues/35
2023-07-13 11:45:53 +02:00
Willi Ballenthin
9e9f120c80 pefile: better handle forwarded exports with specific paths 2023-07-13 10:51:28 +02:00
Capa Bot
546789fea6 Sync capa rules submodule 2023-07-13 08:47:01 +00:00
Willi Ballenthin
76901ced19 Merge pull request #1634 from mandiant/feat/faster-py-tests
ci: use latest python for best performance
2023-07-13 10:45:48 +02:00
Willi Ballenthin
c29d0a4f56 Update .github/workflows/tests.yml
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2023-07-13 10:45:43 +02:00
Willi Ballenthin
6b6d7eb494 pefile: extract forwarded exports 2023-07-13 10:32:27 +02:00
Willi Ballenthin
21b2aac8b5 fixtures: add test cases for forwarded exports 2023-07-13 10:31:52 +02:00
Willi Ballenthin
7898ac24d5 show-features: support showing pefile features 2023-07-13 10:31:28 +02:00
Willi Ballenthin
5a3775455b main: allow to specify --backend=pefile 2023-07-13 10:30:43 +02:00
Willi Ballenthin
892cd48713 Merge pull request #1633 from mandiant/dependabot/pip/ruff-0.0.278
build(deps-dev): bump ruff from 0.0.277 to 0.0.278
2023-07-13 10:24:56 +02:00
dependabot[bot]
c062115366 build(deps-dev): bump ruff from 0.0.277 to 0.0.278
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.0.277 to 0.0.278.
- [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.277...v0.0.278)

---
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-13 08:19:29 +00:00
Willi Ballenthin
ff7a006ba1 Merge pull request #1632 from mandiant/feat/issue-1594
update copyright and license headers
2023-07-13 10:18:50 +02:00
Willi Ballenthin
7665d56f93 Merge branch 'master' into feat/issue-1594 2023-07-13 10:18:44 +02:00
Capa Bot
280e253286 Sync capa rules submodule 2023-07-13 08:15:43 +00:00
Willi Ballenthin
7edf126a63 Merge pull request #1631 from mandiant/feat/issue-1599
introduce flake8-use-pathlib
2023-07-13 10:15:24 +02:00
Willi Ballenthin
ad6b475dfe Merge pull request #1630 from mandiant/fix/issue-1629
fix binja test type error
2023-07-13 10:14:22 +02:00
Capa Bot
f897f00227 Sync capa-testfiles submodule 2023-07-13 08:11:11 +00:00
Willi Ballenthin
ea3090a066 changelog 2023-07-13 09:39:04 +02:00
Willi Ballenthin
b9090b86ce tests: make fixtures available via conftest.py
closes #1592
2023-07-13 09:37:39 +02:00
Capa Bot
5088f45b6a Sync capa-testfiles submodule 2023-07-13 07:19:20 +00:00
Capa Bot
ea51801806 Sync capa-testfiles submodule 2023-07-13 07:06:30 +00:00
Willi Ballenthin
04db034895 changelog 2023-07-13 08:49:46 +02:00
Willi Ballenthin
b547987b33 main: don't show spinner when debug messages are emitted
closes #1636
2023-07-13 08:47:14 +02:00
Willi Ballenthin
0511ef7093 changelog 2023-07-13 06:26:25 +02:00
Willi Ballenthin
e9ccc5276a main: log time taken to analyze each function
closes #1290
2023-07-13 06:24:22 +02:00
Willi Ballenthin
36a840cb2c ci: set top level permissions to satisfy code scanning 2023-07-13 06:12:42 +02:00
Willi Ballenthin
797021874b ci: use latest python for best performance 2023-07-13 05:37:22 +02:00
Willi Ballenthin
2370c5b50d Merge branch 'master' of personal.github.com:mandiant/capa into feat/issue-1594 2023-07-13 05:19:38 +02:00
Willi Ballenthin
b285985a79 flake8: configure copyright header for our project
closes #1594
2023-07-13 05:16:59 +02:00
Willi Ballenthin
59bd930881 fix merge 2023-07-13 05:04:26 +02:00
Willi Ballenthin
c86ab51210 fix copyright headers everywhere 2023-07-13 05:03:33 +02:00
Willi Ballenthin
e987fc2034 flake8: initial copyright config 2023-07-13 04:57:36 +02:00
Willi Ballenthin
7550cc8466 introduce flake8-use-pathlib 2023-07-13 04:31:20 +02:00
Willi Ballenthin
acaf6c1272 main: add type hints for main 2023-07-13 04:25:01 +02:00
Willi Ballenthin
a28000b41a Merge branch 'master' into fix/issue-1629 2023-07-13 04:24:51 +02:00
Willi Ballenthin
560dc358fa Merge branch 'master' into fix/issue-1629 2023-07-13 04:20:04 +02:00
Willi Ballenthin
a32f2cc0f8 tests: fix type error 2023-07-13 04:19:09 +02: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
Capa Bot
87a6459278 Sync capa rules submodule 2023-07-12 10:13:13 +00:00
Willi Ballenthin
4e02e36d2c Merge pull request #1628 from mandiant/feat/flake8-simplify
introduce flake8-simplify
2023-07-12 12:12:53 +02:00
Willi Ballenthin
a35bf4c807 Merge pull request #1626 from mandiant/dependabot/pip/black-23.7.0
build(deps-dev): bump black from 23.3.0 to 23.7.0
2023-07-12 11:44:37 +02:00
Willi Ballenthin
a106953fec Merge pull request #1627 from mandiant/dependabot/pip/flake8-bugbear-23.7.10
build(deps-dev): bump flake8-bugbear from 23.6.5 to 23.7.10
2023-07-12 11:44:26 +02:00
Willi Ballenthin
65e8300145 introduce flake8-simplify 2023-07-12 11:40:44 +02:00
Capa Bot
7526ff876f Sync capa-testfiles submodule 2023-07-12 09:09:04 +00:00
Capa Bot
78a6d9a511 Sync capa rules submodule 2023-07-12 09:06:40 +00: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
dependabot[bot]
2343e73f41 build(deps-dev): bump flake8-bugbear from 23.6.5 to 23.7.10
Bumps [flake8-bugbear](https://github.com/PyCQA/flake8-bugbear) from 23.6.5 to 23.7.10.
- [Release notes](https://github.com/PyCQA/flake8-bugbear/releases)
- [Commits](https://github.com/PyCQA/flake8-bugbear/compare/23.6.5...23.7.10)

---
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-07-12 08:51:34 +00:00
dependabot[bot]
aae2e51688 build(deps-dev): bump black from 23.3.0 to 23.7.0
Bumps [black](https://github.com/psf/black) from 23.3.0 to 23.7.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.3.0...23.7.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-07-12 08:51:25 +00:00
Willi Ballenthin
fe57016abd Merge pull request #1619 from mandiant/dependabot/pip/protobuf-4.23.4
build(deps-dev): bump protobuf from 4.23.2 to 4.23.4
2023-07-12 10:51:02 +02:00
Willi Ballenthin
de8bba41dc Merge pull request #1620 from mandiant/dependabot/pip/ruff-0.0.277
build(deps-dev): bump ruff from 0.0.275 to 0.0.277
2023-07-12 10:50:48 +02:00
Willi Ballenthin
90a2fd936c Merge pull request #1623 from Aayush-Goel-04/Aayush-Goel-04/Issue#1534
Updated file paths to use pathlib.Path instance
2023-07-12 10:50:29 +02:00
Capa Bot
deb6114530 Sync capa rules submodule 2023-07-11 20:38:54 +00: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
Willi Ballenthin
d438b90879 Merge branch 'master' into Aayush-Goel-04/Issue#1534 2023-07-11 12:30:13 +02: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
Capa Bot
c1cd272865 Sync capa-testfiles submodule 2023-07-11 08:29:10 +00:00
Capa Bot
fdb53d97ce Sync capa-testfiles submodule 2023-07-11 08:28:43 +00:00
Capa Bot
db5e735928 Sync capa-testfiles submodule 2023-07-11 08:28:27 +00:00
colton-gabertan
785825d77e Merge branch 'master' into backend-ghidra 2023-07-11 01:00:55 -07:00
Aayush Goel
1baa7a5e4b flake8 checks resolved 2023-07-11 02:30:09 +05:30
Aayush Goel
ef39bc3c3a Merged Changes from PR #1591 2023-07-11 01:14:38 +05:30
Aayush Goel
8e346cb411 Merge branch 'Aayush-Goel-04/Issue#1534' of https://github.com/Aayush-Goel-04/capa into Aayush-Goel-04/Issue#1534 2023-07-11 00:59:21 +05:30
Aayush Goel
d1a1c6875b extractors accept Path instance 2023-07-11 00:41:36 +05:30
Capa Bot
b84af6a205 Sync capa rules submodule 2023-07-10 15:27:03 +00: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
Willi Ballenthin
160c662e7c Merge pull request #1621 from mandiant/dependabot/pip/flake8-comprehensions-3.14.0
build(deps-dev): bump flake8-comprehensions from 3.13.0 to 3.14.0
2023-07-10 16:52:41 +02:00
Yacine Elhamer
63e273efd4 fix bugs and mypy issues 2023-07-10 15:52:33 +01:00
dependabot[bot]
015056c54a build(deps-dev): bump flake8-comprehensions from 3.13.0 to 3.14.0
Bumps [flake8-comprehensions](https://github.com/adamchainz/flake8-comprehensions) from 3.13.0 to 3.14.0.
- [Changelog](https://github.com/adamchainz/flake8-comprehensions/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/adamchainz/flake8-comprehensions/compare/3.13.0...3.14.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>
2023-07-10 14:37:18 +00:00
dependabot[bot]
babf99ea48 build(deps-dev): bump ruff from 0.0.275 to 0.0.277
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.0.275 to 0.0.277.
- [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.275...v0.0.277)

---
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-10 14:36:34 +00:00
dependabot[bot]
c8f5496008 build(deps-dev): bump protobuf from 4.23.2 to 4.23.4
Bumps [protobuf](https://github.com/protocolbuffers/protobuf) from 4.23.2 to 4.23.4.
- [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.2...v4.23.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-10 14:35:50 +00: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
Willi Ballenthin
aa8055229d Merge pull request #1617 from mandiant/fix/issue-1616
ci: restrict permissions of GITHUB_TOKEN
2023-07-10 14:13:33 +02:00
Willi Ballenthin
454b6d1aca Merge branch 'master' into fix/issue-1616 2023-07-10 14:03:39 +02: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
Willi Ballenthin
1373fabf02 Merge pull request #1613 from mandiant/fix/issue-1491
PyPI trusted publishing
2023-07-10 13:48:24 +02:00
Willi Ballenthin
320539bd26 Merge branch 'master' into fix/issue-1491 2023-07-10 13:48:15 +02:00
Willi Ballenthin
ac12d5a7e2 Merge pull request #1611 from mandiant/fix/issue-1301
migrate to pyproject.toml
2023-07-10 13:45:50 +02: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
506d677684 Merge pull request #1591 from mandiant/fix/issue-1579
use pre-commit to invoke linters
2023-07-10 11:58:01 +02:00
Willi Ballenthin
f983307c97 Merge branch 'master' into fix/issue-1579 2023-07-10 11:57:51 +02:00
Capa Bot
a712bf3389 Sync capa rules submodule 2023-07-10 09:57:25 +00: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
Willi Ballenthin
dc1f2e728d ci: restrict permissions of GITHUB_TOKEN
closes #1616
2023-07-10 02:43:48 +02:00
Willi Ballenthin
1f8aa7cfe1 changelog 2023-07-10 02:07:19 +02:00
Willi Ballenthin
81b964386f ci: publish to PyPI using trusted publishing
closes #1491
2023-07-10 02:06:06 +02:00
Willi Ballenthin
cb289e3fc5 ci: publish: use trusted publishing 2023-07-10 01:57:42 +02:00
Willi Ballenthin
fb176196eb changelog 2023-07-10 01:46:06 +02:00
Willi Ballenthin
dd2bbc9a48 migrate to pyproject.toml
closes #1301
2023-07-10 01:44:38 +02:00
Willi Ballenthin
118b955e10 features: fix circular import 2023-07-09 23:59:45 +02:00
Willi Ballenthin
d89dd499b6 add issue links for TODOs 2023-07-09 23:55:36 +02:00
Willi Ballenthin
430f9da449 Merge branch 'master' into fix/issue-1579 2023-07-10 11:09:25 +02:00
Willi Ballenthin
ae10a2ea34 introduce flake8-todos linter 2023-07-09 23:35:52 +02:00
Willi Ballenthin
4a49543d12 introduce flake8-print linter 2023-07-09 22:44:47 +02:00
Willi Ballenthin
106b12e2a4 move flake8 config to its own config file 2023-07-09 22:35:53 +02:00
Willi Ballenthin
7fe738e28f introduce flake8-no-implicit-concat linter 2023-07-09 22:18:01 +02:00
Willi Ballenthin
54203f3be9 introduce flake8-logging-format linter 2023-07-09 22:11:46 +02:00
Aayush Goel
a949698b86 Update fixtures.py
Dealt with encoding methods for how "ping_täst" file name is read.
2023-07-09 17:47:09 +05:30
Aayush Goel
673af45c55 Update args.sample type to Path and str vs as_posix comparisons 2023-07-09 16:02:28 +05:30
Aayush Goel
e0ed8c6e04 Resolved the suggestions. 2023-07-08 13:51:41 +05:30
Capa Bot
fc1dd401d2 Sync capa rules submodule 2023-07-08 07:53:28 +00: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
4a2902512e Update test_binja_features.py (#1595)
temporarily skip stack string test, while we wait for #1473
2023-07-07 14:01:50 +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
Aayush Goel
a8f1067f8a Fixed Path issue in cache-ruleset.py 2023-07-07 12:39:18 +05:30
Aayush Goel
ef9b0737a8 Merge branch 'master' into Aayush-Goel-04/Issue#1534 2023-07-07 12:05:57 +05:30
Aayush Goel
6218f31ea2 Update CHANGELOG.md
Update CHANGELOG.md

Update CHANGELOG.md

Update CHANGELOG.md
2023-07-07 12:03:05 +05:30
Aayush Goel
14924174c5 convert str(path) usage to path.as_posix() to get str format of Path
Update fixtures.py
2023-07-07 12:03:05 +05:30
Aayush Goel
edeb458b33 some more changes 2023-07-07 12:03:05 +05:30
Capa Bot
b8f277b3c6 Sync capa-testfiles submodule 2023-07-07 06:26:53 +00:00
Capa Bot
5bc85f39a6 Sync capa rules submodule 2023-07-07 06:26:34 +00: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
Willi Ballenthin
13a8e252f0 introduce flake8-comprehensions 2023-07-06 20:04:27 +02:00
Willi Ballenthin
ff47270681 add flake8-encoding plugin 2023-07-06 19:42:57 +02:00
Willi Ballenthin
3ad4de70bf gitignore 2023-07-06 19:35:17 +02:00
Willi Ballenthin
9f6165f65c doc: installation: better enumerate current linters 2023-07-06 19:34:07 +02:00
Willi Ballenthin
982dc46623 add flake8-bugbear linter 2023-07-06 19:30:51 +02:00
Yacine Elhamer
a8f722c4de xfail tests that require the old ruleset 2023-07-06 18:15:02 +01:00
Willi Ballenthin
a43d2c115f tests: fix fixture imports 2023-07-06 19:04:53 +02: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
Willi Ballenthin
e675bef062 ci: invoke linter directly 2023-07-06 18:14:14 +02:00
Willi Ballenthin
511aa0fb51 doc: installation: more details on pre-commit 2023-07-06 18:11:58 +02:00
Willi Ballenthin
90e607fe9a flake8 2023-07-06 18:11:48 +02:00
Willi Ballenthin
9441da4887 isort 2023-07-06 17:50:34 +02:00
Willi Ballenthin
47074fd129 fix ruff issues 2023-07-06 17:49:40 +02:00
Willi Ballenthin
adbfb8db06 doc: installation: document pre-commit 2023-07-06 17:18:36 +02:00
Willi Ballenthin
8c8601197b changelog 2023-07-06 17:15:16 +02:00
Willi Ballenthin
3ca233e0bd Merge branch 'master' into fix/issue-1579 2023-07-07 10:46:09 +02:00
Willi Ballenthin
f17edb3151 ci: use pre-commit to invoke linters 2023-07-06 17:12:19 +02:00
Willi Ballenthin
691ef1c72f remove old linter configs 2023-07-06 17:12:00 +02:00
Willi Ballenthin
75a76b47be setup: add pre-commit dev dependency 2023-07-06 17:11:37 +02:00
Willi Ballenthin
6f0d1f7518 add pre-commit config 2023-07-06 17:10:54 +02:00
Willi Ballenthin
25a6d78b88 ruff: update config 2023-07-06 16:32:31 +02:00
Willi Ballenthin
65e309450d Merge pull request #1588 from mandiant/fix/feature-1586
use fancy box drawing characters for default output
2023-07-06 15:26:24 +02:00
Willi Ballenthin
51292880fd Merge branch 'master' into fix/feature-1586 2023-07-06 15:26:08 +02:00
Willi Ballenthin
26998efead Merge pull request #1589 from mandiant/fix/dont-leave-tqdm
main: don't leave behind traces of the progress bar
2023-07-06 15:22:48 +02:00
Willi Ballenthin
cf9421aabf Merge branch 'master' into fix/dont-leave-tqdm 2023-07-06 15:22:42 +02:00
Willi Ballenthin
e53fd8d6c8 Merge pull request #1587 from mandiant/fix/issue-1578
bump minimum python version to 3.8
2023-07-06 15:22:07 +02:00
Willi Ballenthin
b62c011823 Merge branch 'master' into fix/issue-1578 2023-07-06 14:36:58 +02:00
Willi Ballenthin
f9248262f5 Merge branch 'master' into fix/dont-leave-tqdm 2023-07-06 14:36:43 +02:00
Moritz
bbafedc992 Merge pull request #1585 from mandiant/fix/issue-1584
fix import-to-ida due to changes in the result document format in v5
2023-07-06 14:33:01 +02:00
Capa Bot
46ff798fae Sync capa-testfiles submodule 2023-07-06 09:26:23 +00: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
Capa Bot
b57188e98c Sync capa rules submodule 2023-07-06 08:17:32 +00:00
Capa Bot
49ffbdd54d Sync capa-testfiles submodule 2023-07-06 08:04:33 +00: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
Aayush Goel
62db346b49 Style , mypy checks 2023-07-06 05:28:13 +05:30
Yacine Elhamer
47aebcbdd4 fix show-capabilities-by-function 2023-07-06 00:48:22 +01:00
Aayush Goel
20e7acaa1a Update CHANGELOG.md 2023-07-06 05:16:27 +05:30
Aayush Goel
c0d712acea Changes os.path to pathlib.Path usage
changed args.rules , args.signatures types in handle_common_args.
2023-07-06 05:12:50 +05:30
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
Aayush Goel
66e2a225d2 Merge branch 'mandiant:master' into Aayush-Goel-04/Issue#1534 2023-07-06 02:21:11 +05:30
Willi Ballenthin
2e27745b5f setup: bump mypy hints for colorama 2023-07-05 19:30:55 +02:00
Willi Ballenthin
b5a063b0d9 pep8 2023-07-05 19:19:26 +02:00
Willi Ballenthin
ba8040ace5 main: remove old codec registration for py3.7 2023-07-05 19:15:33 +02:00
Willi Ballenthin
9bcd7678a4 main: fix console output on windows (in CI) 2023-07-05 19:14:15 +02:00
Willi Ballenthin
23ed0a5d9d main: don't leave behind traces of the progress bar 2023-07-05 19:06:33 +02:00
Willi Ballenthin
2b6cc6fee2 changelog 2023-07-05 18:57:37 +02:00
Willi Ballenthin
6a76760033 render: use fancy boxes
closes #1586
2023-07-05 18:55:32 +02:00
Willi Ballenthin
dd2d5431a9 setup: bump networkx to 3.1 since we now have python 3.8 as min version 2023-07-05 18:44:12 +02:00
Willi Ballenthin
5d1e26a95e update minimum supported python version to 3.8 2023-07-05 18:34:41 +02:00
Willi Ballenthin
bf5b2612c8 changelog 2023-07-05 18:27:20 +02:00
Willi Ballenthin
694143ce6b import-to-ida: use Metadata type not json document 2023-07-05 18:24:37 +02:00
Willi Ballenthin
19a5ef8a64 import-to-ida: use existing result document json parser 2023-07-05 18:21:03 +02:00
Willi Ballenthin
169b3d60a8 import-to-ida: update to use v5 JSON format
closes #1584
2023-07-05 18:04:15 +02:00
Willi Ballenthin
bb053561ef import-to-ida: decode MD5 to hex 2023-07-05 18:03:57 +02: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
Moritz
b1eda6c24d Merge pull request #1568 from mandiant/update-lint-data
update att&ck/mbc data via script
2023-07-05 13:11:22 +02:00
mr-tz
1a2e034ee0 update data via script 2023-07-05 12:30:54 +02:00
Capa Bot
a6763d8882 Sync capa rules submodule 2023-07-05 08:59:18 +00:00
Capa Bot
16ce6a5ef2 Sync capa rules submodule 2023-07-05 08:57:27 +00:00
Capa Bot
0a74eb671f Sync capa rules submodule 2023-07-05 06:58:23 +00:00
Capa Bot
0c3c5e42ff Sync capa rules submodule 2023-07-05 06:41:40 +00:00
Capa Bot
1e258c3bc2 Sync capa rules submodule 2023-07-05 06:41:20 +00:00
Capa Bot
2d55976cb4 Sync capa rules submodule 2023-07-05 06:40:30 +00:00
Capa Bot
9a7ce0b048 Sync capa-testfiles submodule 2023-07-04 08:55:21 +00:00
Capa Bot
446114acc3 Sync capa-testfiles submodule 2023-07-04 08:54:56 +00:00
Capa Bot
30950f129e Sync capa-testfiles submodule 2023-07-04 08:54:40 +00:00
Yacine Elhamer
c042a28af1 rename Flavor to Scopes 2023-07-03 19:21:08 +01:00
Capa Bot
066e42e271 Sync capa-testfiles submodule 2023-07-03 14:05:29 +00:00
Capa Bot
301d8425c1 Sync capa-testfiles submodule 2023-07-03 14:05:01 +00:00
Capa Bot
165fe87aca Sync capa-testfiles submodule 2023-07-03 14:04:39 +00: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
Capa Bot
06dd6f45c0 Sync capa rules submodule 2023-07-03 07:54:42 +00: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
Capa Bot
2cd6b8bdac Sync capa-testfiles submodule 2023-06-29 10:01:38 +00:00
Capa Bot
7ab2a9b163 Sync capa-testfiles submodule 2023-06-29 09:47:46 +00: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
Capa Bot
4548303a0c Sync capa rules submodule 2023-06-28 06:25:24 +00: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
Aayush Goel
4ceff605bf Merge branch 'mandiant:master' into Aayush-Goel-04/Issue#1534 2023-06-27 18:06:57 +05:30
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
Willi Ballenthin
39bb4ed842 Merge pull request #1570 from mandiant/dependabot/pip/ruff-0.0.275
build(deps-dev): bump ruff from 0.0.270 to 0.0.275
2023-06-27 09:34:23 +02:00
dependabot[bot]
8edeb0e6e8 build(deps-dev): bump ruff from 0.0.270 to 0.0.275
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.0.270 to 0.0.275.
- [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.270...v0.0.275)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-27 07:33:03 +00:00
Willi Ballenthin
e3b58eac67 Merge pull request #1573 from mandiant/dependabot/pip/mypy-1.4.1
build(deps-dev): bump mypy from 1.3.0 to 1.4.1
2023-06-27 09:32:25 +02:00
Willi Ballenthin
8b23a86d2e Merge branch 'master' into dependabot/pip/mypy-1.4.1 2023-06-27 09:32:14 +02:00
Willi Ballenthin
d95acc9734 Merge pull request #1574 from mandiant/dependabot/pip/pytest-7.4.0
build(deps-dev): bump pytest from 7.3.1 to 7.4.0
2023-06-27 09:32:03 +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
dependabot[bot]
7c72b56a4e build(deps-dev): bump pytest from 7.3.1 to 7.4.0
Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.3.1 to 7.4.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.3.1...7.4.0)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-26 14:58:39 +00:00
dependabot[bot]
8429d6b8e2 build(deps-dev): bump mypy from 1.3.0 to 1.4.1
Bumps [mypy](https://github.com/python/mypy) from 1.3.0 to 1.4.1.
- [Commits](https://github.com/python/mypy/compare/v1.3.0...v1.4.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-26 14:58:26 +00: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
Aayush Goel
842f76c8bd Merge branch 'mandiant:master' into Aayush-Goel-04/Issue#1534 2023-06-26 00:35:55 +05:30
Aayush Goel
157dfac527 Current os.apth to pathlib.Path
need to update args type

Revert "Current os.apth to pathlib.Path"

This reverts commit 170fe9ad93b0a4d44a08470633133c0d32ccef24.
2023-06-26 00:34:12 +05:30
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
Capa Bot
a92d91e82a Sync capa rules submodule 2023-06-24 08:21:24 +00: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
Capa Bot
33a3170bc4 Sync capa rules submodule 2023-06-22 07:11:54 +00: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
Willi Ballenthin
2ce4f8769d Merge pull request #1513 from mandiant/ida-test-runner
tests: refine the IDA test runner
2023-06-20 14:28:12 +02:00
Willi Ballenthin
4dedc24f9f Merge branch 'master' into ida-test-runner 2023-06-20 14:28:05 +02: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
Moritz
1bc0174f6f Merge pull request #1562 from mandiant/dependabot/pip/ruamel-yaml-0.17.32
build(deps): bump ruamel-yaml from 0.17.28 to 0.17.32
2023-06-19 17:24:22 +02:00
Moritz
90842f313a Merge pull request #1543 from mandiant/dependabot/pip/pydantic-1.10.9
build(deps): bump pydantic from 1.10.7 to 1.10.9
2023-06-19 17:23:51 +02:00
Moritz
6aa2f6457c Merge pull request #1521 from mandiant/dependabot/pip/pytest-cov-4.1.0
build(deps-dev): bump pytest-cov from 4.0.0 to 4.1.0
2023-06-19 17:23:19 +02:00
Moritz
b7c600e60b Merge pull request #1520 from mandiant/dependabot/pip/requests-2.31.0
build(deps-dev): bump requests from 2.28.0 to 2.31.0
2023-06-19 17:22:55 +02:00
Moritz
d397b46b63 Merge pull request #1518 from mandiant/dependabot/pip/types-requests-2.31.0.1
build(deps-dev): bump types-requests from 2.28.1 to 2.31.0.1
2023-06-19 17:22:32 +02:00
dependabot[bot]
7a6b7c5ef0 build(deps): bump ruamel-yaml from 0.17.28 to 0.17.32
Bumps [ruamel-yaml](https://sourceforge.net/p/ruamel-yaml/code/ci/default/tree) from 0.17.28 to 0.17.32.

---
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-06-19 14:58:25 +00: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
Stephen Eckels
7ef78fdbce 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-13 12:00:06 -06: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
dependabot[bot]
366c55231e build(deps): bump pydantic from 1.10.7 to 1.10.9
Bumps [pydantic](https://github.com/pydantic/pydantic) from 1.10.7 to 1.10.9.
- [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/v1.10.7...v1.10.9)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-12 14:58:23 +00:00
Capa Bot
43b2ee3c52 Sync capa rules submodule 2023-06-12 12:28:18 +00:00
Capa Bot
85a7c87830 Sync capa rules submodule 2023-06-12 12:18:23 +00:00
Willi Ballenthin
2d7e20f532 Merge pull request #1527 from xusheng6/fix_bn_unit_test
Update the stack string detection with BN's builtin outlining of constant expressionss
2023-06-12 10:41:15 +02:00
Capa Bot
cc993b67a3 Sync capa rules submodule 2023-06-12 06:58:29 +00: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
Xusheng
a74911e926 Add a test that asserts on the binja version 2023-06-09 13:44:07 +08:00
Xusheng
8cc16e8de9 Update the stack string detection with BN's builtin outlining of constant expressions 2023-06-09 13:41:53 +08:00
Yacine Elhamer
35e53e9691 add abstract DynamicExtractor class 2023-06-08 23:15:29 +00:00
Capa Bot
0559e61af1 Sync capa rules submodule 2023-06-08 08:41:14 +00:00
colton-gabertan
3da233dcad colton: removed redundant imports & object, locally tested 2023-06-07 13:04:49 -07:00
Capa Bot
2fe0713faa Sync capa rules submodule 2023-06-07 10:17:28 +00:00
Willi Ballenthin
28629b352c Merge pull request #1502 from Aayush-Goel-04/Aayush-Goel-04/Issue#1411
Update Metadata type in capa main
2023-06-06 13:04:35 +02:00
Aayush Goel
e5f79c9f5c Merge branch 'mandiant:master' into Aayush-Goel-04/Issue#1411 2023-06-06 13:04:19 +05:30
Aayush Goel
c6815ef126 Update Model and FrozenModel Class 2023-06-06 13:02:30 +05:30
dependabot[bot]
28b2cd5117 build(deps-dev): bump pytest-cov from 4.0.0 to 4.1.0
Bumps [pytest-cov](https://github.com/pytest-dev/pytest-cov) from 4.0.0 to 4.1.0.
- [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-cov/compare/v4.0.0...v4.1.0)

---
updated-dependencies:
- dependency-name: pytest-cov
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-05 14:58:21 +00:00
dependabot[bot]
28c24c9d48 build(deps-dev): bump requests from 2.28.0 to 2.31.0
Bumps [requests](https://github.com/psf/requests) from 2.28.0 to 2.31.0.
- [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.28.0...v2.31.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-05 14:58:17 +00:00
dependabot[bot]
b2080cdfbc build(deps-dev): bump types-requests from 2.28.1 to 2.31.0.1
Bumps [types-requests](https://github.com/python/typeshed) from 2.28.1 to 2.31.0.1.
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-05 14:58:02 +00:00
Willi Ballenthin
57095175d2 Merge pull request #1443 from yelhamer/feature-static-api-names
Extract api names from ELF debug symbols [vivisect]
2023-06-05 14:54:34 +02:00
Yacine Elhamer
5b260c00f4 fix symtab FunctionName feature scope address 2023-06-05 13:37:19 +01:00
Yacine Elhamer
9b0fb74d94 fix typo: "Elf" to "elf" 2023-06-05 13:36:50 +01:00
Yacine Elhamer
103b384c09 fix viv/extractor.py codestyle imports 2023-06-05 12:17:27 +01:00
Yacine Elhamer
65f18aecc8 fix mypy typing issues 2023-06-05 12:14:56 +01:00
Yacine Elhamer
e971bc4044 fix codestyle issues 2023-06-05 12:01:39 +01:00
Aayush Goel
b4870b120e Remove from_capa API for MetaData 2023-06-03 15:33:49 +05:30
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
Yacine Elhamer
7dff76b122 Merge branch 'master' into feature-static-api-names 2023-06-03 01:44:13 +01:00
Yacine Elhamer
be5ada26ea fix code style 2023-06-03 01:12:56 +01:00
Yacine Elhamer
5b903ca4f3 add error handling to SymTab and its callers 2023-06-02 23:19:14 +01:00
Yacine Elhamer
6b2710ac7e fix broken logic in extract_function_symtab_names() 2023-06-02 22:43:58 +01:00
Yacine Elhamer
764fda8e7b add missing Shdr.from_viv() method 2023-06-02 17:57:37 +01:00
Yacine Elhamer
151ef95b79 remove usage of vsGetField 2023-06-02 17:14:44 +01:00
Yacine Elhamer
4976375d74 elf.py: fix identation error 2023-06-02 16:30:17 +01:00
Yacine Elhamer
0b834a1623 delete functionName extraction at instruction level
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-06-02 15:56:14 +01:00
Yacine Elhamer
41c512624b update symtab-based FunctionName feature extraction 2023-06-02 14:44:51 +01:00
Yacine Elhamer
9467ee6f10 add FunctionName extraction at the function scope 2023-06-02 14:42:04 +01:00
Yacine Elhamer
dde76e301d add a method to construct SymTab objects from Elf objects 2023-06-02 12:15:05 +01:00
Aayush Goel
5ded85f46e Update CHANGELOG.md 2023-06-02 14:54:36 +05:30
Capa Bot
0cbe4618e1 Sync capa-testfiles submodule 2023-06-02 09:20:23 +00:00
Aayush Goel
f03ad2d208 Merge branch 'mandiant:master' into Aayush-Goel-04/Issue#1411 2023-06-02 14:47:24 +05:30
Willi Ballenthin
8b867836e9 changelog 2023-06-02 10:45:05 +02:00
Willi Ballenthin
236c1c9d17 tests: refine the IDA test runner
ref #1364
2023-06-02 10:40:47 +02:00
Willi Ballenthin
64dca7d801 Merge branch 'master' into feature-static-api-names 2023-06-02 09:26:25 +02:00
Willi Ballenthin
3834314c2a Merge pull request #1463 from Aayush-Goel-04/Aayush-Goel-04/Issue#1451
Utility script to detect feature overlap between new and existing CAPA rules.
2023-06-02 09:18:00 +02:00
Willi Ballenthin
144723be3c Merge pull request #1496 from mandiant/dependabot/pip/ruamel-yaml-0.17.28
build(deps): bump ruamel-yaml from 0.17.21 to 0.17.28
2023-06-02 09:16:29 +02:00
Capa Bot
0f54a6f67e Sync capa rules submodule 2023-06-02 07:13:58 +00:00
Yacine Elhamer
1cec768521 fix strtab renaming error 2023-06-01 22:20:23 +01:00
Yacine Elhamer
d85d01eea1 use the function-handle's cache instead of the VivWorkspace file metadata 2023-06-01 22:15:47 +01:00
Yacine Elhamer
8d1e1cc54c fix strtab naming 2023-06-01 21:56:34 +01:00
Aayush Goel
0d9e74028e Update Metadata 2023-06-02 01:19:42 +05:30
Aayush Goel
445214b23b Update Metadata type in capa main 2023-06-02 00:40:38 +05:30
colton-gabertan
16444fe5ed first working CI install 2023-06-01 11:24:21 -07:00
Yacine Elhamer
994edf66fe return the target's address for the function-name feature 2023-06-01 12:45:49 +01:00
Yacine Elhamer
f9291d4e50 extract symtab-api names before processing library functions 2023-06-01 12:45:10 +01:00
Yacine Elhamer
ab089c024d fetch section data by offset (not name)
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-06-01 11:46:39 +01:00
Yacine Elhamer
ffb1cb3128 rename strtab to strtab_section
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-06-01 10:26:40 +01:00
Yacine Elhamer
57386812f9 use ELF class member instead of vsGetField()
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-06-01 10:26:21 +01:00
Willi Ballenthin
ce8e15a220 Merge branch 'master' into feature-static-api-names 2023-06-01 09:39:07 +02:00
Yacine Elhamer
0d42ac3912 add missing function-name feature testing 2023-06-01 02:14:25 +01:00
Yacine Elhamer
f10a43abe6 fix style issues 2023-06-01 02:02:40 +01:00
Yacine Elhamer
64ef2c8a65 add tests for vivisect's usage of debug symbols 2023-06-01 01:50:06 +01:00
Capa Bot
d3c44a8263 Sync capa rules submodule 2023-05-31 18:16:12 +00:00
Moritz
8d016de217 Merge pull request #1494 from mandiant/dependabot/pip/protobuf-4.23.2
build(deps): bump protobuf from 4.22.3 to 4.23.2
2023-05-31 07:54:15 +02:00
Moritz
ee3d3a964e Merge pull request #1483 from mandiant/dependabot/pip/types-protobuf-4.23.0.1
build(deps-dev): bump types-protobuf from 4.22.0.2 to 4.23.0.1
2023-05-31 07:53:53 +02:00
Aayush Goel
d6e145936d Merge branch 'mandiant:master' into Aayush-Goel-04/Issue#1451 2023-05-31 00:26:48 +05:30
Capa Bot
9caea57cde Sync capa rules submodule 2023-05-30 14:37:56 +00:00
Capa Bot
99e81e1d8f Sync capa rules submodule 2023-05-30 14:31:43 +00:00
Capa Bot
1696a9ad2d Sync capa-testfiles submodule 2023-05-30 14:28:43 +00:00
Willi Ballenthin
6c2a83dda8 Merge pull request #1495 from mandiant/dependabot/pip/ruff-0.0.270
build(deps-dev): bump ruff from 0.0.265 to 0.0.270
2023-05-30 12:02:16 +02: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
dependabot[bot]
c113a3b5b8 build(deps): bump ruamel-yaml from 0.17.21 to 0.17.28
Bumps [ruamel-yaml](https://sourceforge.net/p/ruamel-yaml/code/ci/default/tree) from 0.17.21 to 0.17.28.

---
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-05-29 14:59:13 +00:00
dependabot[bot]
a07b47c845 build(deps-dev): bump ruff from 0.0.265 to 0.0.270
Bumps [ruff](https://github.com/charliermarsh/ruff) from 0.0.265 to 0.0.270.
- [Release notes](https://github.com/charliermarsh/ruff/releases)
- [Changelog](https://github.com/charliermarsh/ruff/blob/main/BREAKING_CHANGES.md)
- [Commits](https://github.com/charliermarsh/ruff/compare/v0.0.265...v0.0.270)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-29 14:59:02 +00:00
dependabot[bot]
f789e144fd build(deps): bump protobuf from 4.22.3 to 4.23.2
Bumps [protobuf](https://github.com/protocolbuffers/protobuf) from 4.22.3 to 4.23.2.
- [Release notes](https://github.com/protocolbuffers/protobuf/releases)
- [Changelog](https://github.com/protocolbuffers/protobuf/blob/main/generate_changelog.py)
- [Commits](https://github.com/protocolbuffers/protobuf/compare/v4.22.3...v4.23.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-29 14:58:10 +00:00
colton-gabertan
78bd5e1e3b colton: tests.yml installs Java, Ghidra, and Ghidrathon 2023-05-28 19:04:31 -07:00
Aayush Goel
2e534a4128 Merge branch 'mandiant:master' into Aayush-Goel-04/Issue#1451 2023-05-27 14:14:32 +05:30
colton-gabertan
50afc2f9b2 colton: developing ghidra backend tests 2023-05-26 17:51:48 -07:00
Capa Bot
e068ce7bc9 Sync capa rules submodule 2023-05-26 08:34:57 +00:00
Aayush Goel
2daf880e39 Merge branch 'mandiant:master' into Aayush-Goel-04/Issue#1451 2023-05-25 13:41:30 +05:30
Willi Ballenthin
7897fa9f29 Merge pull request #1493 from Aayush-Goel-04/Aayush-Goel-04/Issue#749
Add logging redirect to capa main
2023-05-25 09:47:03 +02:00
Aayush Goel
456d4272ab Add logging redirect to capa main 2023-05-25 12:50:42 +05:30
Aayush Goel
52c3ea733b Update tests/test_scripts.py
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2023-05-24 15:39:24 +05:30
Aayush Goel
acdaeb26d3 Update test_scripts.py 2023-05-20 13:09:48 +05:30
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
Capa Bot
932066bc0e Sync capa rules submodule 2023-05-19 08:22:32 +00:00
Aayush Goel
66ea0451e9 Merge branch 'mandiant:master' into Aayush-Goel-04/Issue#1451 2023-05-18 16:30:08 +05:30
Willi Ballenthin
bc05118ee7 Merge pull request #1488 from Aayush-Goel-04/Aayush-Goel-04/Issue#749
Add redirect print to tqdm for capa main
2023-05-18 08:45:45 +02:00
Aayush Goel
275386806d Add redirect print to capa main 2023-05-17 23:57:52 +05:30
Aayush Goel
0afc16fd02 Update test rules to test script 2023-05-17 23:31:37 +05:30
Aayush Goel
6cafe14060 Merge branch 'mandiant:master' into Aayush-Goel-04/Issue#1451 2023-05-17 12:09:26 +05:30
Willi Ballenthin
ad611c2058 Merge pull request #1480 from Aayush-Goel-04/Aayush-Goel-04/Issue#1446
Create test binja backend when invoking standalone capa.exe
2023-05-16 22:10:10 +02:00
Aayush Goel
b876adbc27 Update CHANGELOG.md 2023-05-16 20:22:54 +05:30
Aayush Goel
e428b74657 run test on PMA 01-01.exe_ 2023-05-16 12:23:00 +05:30
Willi Ballenthin
7ab083f19a Merge pull request #1482 from mandiant/dependabot/pip/mypy-1.3.0
build(deps-dev): bump mypy from 1.2.0 to 1.3.0
2023-05-15 20:54:08 +02:00
Aayush Goel
931dcb1dc5 Update test_scripts.py 2023-05-15 23:35:11 +05:30
Aayush Goel
12c191582f Update tests/test_scripts.py
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2023-05-15 22:58:19 +05:30
dependabot[bot]
d861b0798e build(deps-dev): bump types-protobuf from 4.22.0.2 to 4.23.0.1
Bumps [types-protobuf](https://github.com/python/typeshed) from 4.22.0.2 to 4.23.0.1.
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-15 14:58:08 +00:00
dependabot[bot]
b6e85b878e build(deps-dev): bump mypy from 1.2.0 to 1.3.0
Bumps [mypy](https://github.com/python/mypy) from 1.2.0 to 1.3.0.
- [Commits](https://github.com/python/mypy/compare/v1.2.0...v1.3.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-15 14:58:04 +00:00
Aayush Goel
807efec40f Create RuleSet to test overlap script 2023-05-12 22:44:26 +05:30
Aayush Goel
41ff457d65 Update tests/test_scripts.py
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2023-05-12 16:53:44 +05:30
Capa Bot
e605dfb483 Sync capa-testfiles submodule 2023-05-12 08:49:03 +00:00
Aayush Goel
2511f40ab8 Merge branch 'mandiant:master' into Aayush-Goel-04/Issue#1451 2023-05-12 02:37:15 +05:30
Aayush Goel
61554dbaf0 Merge branch 'mandiant:master' into Aayush-Goel-04/Issue#1446 2023-05-12 02:36:56 +05:30
Aayush Goel
ce56ab71d4 Update test_binja_features.py
Not sure which file to use to test capa.main
2023-05-12 02:17:09 +05:30
Willi Ballenthin
21c2705827 Merge pull request #1479 from Aayush-Goel-04/Aayush-Goel-04/Issue#1341
Improved layout to exclude functions with no basic block.
2023-05-11 21:40:56 +02:00
Aayush Goel
916db6c197 Update main.py 2023-05-11 19:40:52 +05:30
Aayush Goel
562e03d2d2 Update CHANGELOG.md
Update CHANGELOG.md

Update main.py
2023-05-11 18:59:29 +05:30
Aayush Goel
eca86470c6 Update test_scripts.py
RULE_CONTENT can be modified as required
2023-05-11 14:12:52 +05:30
Capa Bot
a90eda50a7 Sync capa rules submodule 2023-05-11 08:06:38 +00:00
Aayush Goel
187a4712cb Update test_scripts.py
Here new_rule_path and expected_overlaps will be changed based on the new test rule designed.
Adding tests to check if the code works fine
2023-05-10 20:55:22 +05:30
Capa Bot
58bbb8e3a4 Sync capa-testfiles submodule 2023-05-10 14:10:33 +00:00
Willi Ballenthin
d57ed97f9d Merge pull request #1477 from mandiant/dependabot/pip/ruff-0.0.265
build(deps-dev): bump ruff from 0.0.262 to 0.0.265
2023-05-10 13:45:33 +02:00
dependabot[bot]
b7b451dace build(deps-dev): bump ruff from 0.0.262 to 0.0.265
Bumps [ruff](https://github.com/charliermarsh/ruff) from 0.0.262 to 0.0.265.
- [Release notes](https://github.com/charliermarsh/ruff/releases)
- [Changelog](https://github.com/charliermarsh/ruff/blob/main/BREAKING_CHANGES.md)
- [Commits](https://github.com/charliermarsh/ruff/compare/v0.0.262...v0.0.265)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-08 14:58:18 +00:00
Aayush Goel
d91070c116 Update detect_duplicate_features.py 2023-05-08 20:17:29 +05:30
Aayush Goel
39d2a70679 Update detect_duplicate_features.py
Using get_rules menthod to get set of all existing rules.
2023-05-08 17:29:01 +05:30
Aayush Goel
ec6b6a2266 Update detect_duplicate_features.py 2023-05-08 14:58:30 +05:30
Aayush Goel
9eacf72366 Update detect_duplicate_features.py
loading yaml file using capa.rule.Rule.from_yaml.
Returning any exception/errors occuring while checking the files.
2023-05-06 17:36:13 +05:30
Aayush Goel
30516c33b7 Update detect_duplicate_features.py
Improved parse routine based on suggestions.

Co-Authored-By: Moritz <mr-tz@users.noreply.github.com>
2023-05-05 15:17:43 +05:30
Aayush Goel
615628805c Merge branch 'mandiant:master' into Aayush-Goel-04/Issue#1451 2023-05-04 20:04:28 +05:30
Moritz
8bac455bc9 Merge pull request #1472 from Aayush-Goel-04/Aayush-Goel-04/update_CHANGELOG.md
Update CHANGELOG.md
2023-05-04 16:26:55 +02:00
Aayush Goel
0945d9aea2 Update CHANGELOG.md 2023-05-04 19:55:17 +05:30
Aayush Goel
45c6e74945 Update CHANGELOG.md 2023-05-04 19:32:20 +05:30
Aayush Goel
b32ab87bb7 Merge branch 'mandiant:master' into Aayush-Goel-04/Issue#1451 2023-05-04 19:20:13 +05:30
Willi Ballenthin
8d2a186b1a Merge pull request #1471 from Aayush-Goel-04/Aayush-Goel-04/Issue#1458
Added try/except blocks to detect_elf_os in elf.py for improved ELF parsing and OS detection
2023-05-04 15:19:06 +02:00
Aayush Goel
a62996420f Update elf.py
corrected pre-formatted strings
2023-05-04 18:29:15 +05:30
Aayush Goel
7dc4c44393 Update elf.py
Added more try/excepts around the parsing code in detect_elf_os
2023-05-04 17:13:07 +05:30
Moritz
6ffcbfef3d Merge pull request #1469 from mr-tz/mr-tz-patch-1
Don't test BN - attempt 3
2023-05-04 13:33:36 +02:00
Aayush Goel
1c558a203d Update detect_duplicate_features.py
Added a main routine and using argparse to retrieve these from the command line
2023-05-03 22:32:22 +05:30
Moritz
ed5dabe432 Update tests.yml 2023-05-03 18:16:23 +02:00
Capa Bot
ce28d60edf Sync capa rules submodule 2023-05-02 10:28:10 +00:00
Capa Bot
afa9410209 Sync capa rules submodule 2023-05-02 09:43:49 +00:00
Aayush Goel
09865ccd9b Fixes Linting Issues
Update detect_duplicate_features.py
2023-04-27 06:46:02 +05:30
Aayush Goel
256611bef5 Create detect_duplicate_features.py
Fixes #1451
Python script to detect feature overlap between new and existing CAPA rules. Checks if the a feature in new rules exists in an existing rule
2023-04-27 06:00:38 +05:30
Capa Bot
7b0fac27dc Sync capa rules submodule 2023-04-25 19:19:19 +00:00
Yacine Elhamer
c7b65cfe8a Shdr constructor: Use direct member access to get vstruct's section header information
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-04-25 17:23:32 +01:00
Moritz
f811b6b803 Merge pull request #1449 from mandiant/dependabot/pip/pyinstaller-5.10.1
build(deps-dev): bump pyinstaller from 5.9.0 to 5.10.1
2023-04-25 14:08:07 +02:00
Moritz
ba43513172 Merge pull request #1435 from Vector35/fix_bn_path_detection
Fix BN installation path detection does not work with Python 3.11
2023-04-25 11:37:34 +02:00
dependabot[bot]
f3bb2169c0 build(deps-dev): bump pyinstaller from 5.9.0 to 5.10.1
Bumps [pyinstaller](https://github.com/pyinstaller/pyinstaller) from 5.9.0 to 5.10.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/v5.9.0...v5.10.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-25 09:36:26 +00:00
dependabot[bot]
68b58f979b build(deps): bump termcolor from 2.2.0 to 2.3.0 (#1459)
* build(deps): bump termcolor from 2.2.0 to 2.3.0

Bumps [termcolor](https://github.com/termcolor/termcolor) from 2.2.0 to 2.3.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.2.0...2.3.0)

---
updated-dependencies:
- dependency-name: termcolor
  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>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-25 11:35:34 +02:00
Moritz
8e80bc844d Test BN 2 (#1462)
* Update .github/workflows/tests.yml
2023-04-25 11:35:07 +02:00
Willi Ballenthin
a45cab06d3 Merge pull request #1461 from mandiant/dependabot/pip/ruff-0.0.262
build(deps-dev): bump ruff from 0.0.260 to 0.0.262
2023-04-25 10:28:18 +02:00
Yacine Elhamer
695508aa4c insn.py: Update extract_insn_api_features() to optimize by means of viv rather than function attributes 2023-04-25 08:42:53 +01:00
Moritz
957083d805 fix ELF parse error (#1454)
* fix ELF parse error

* add ELF header parsing test
2023-04-25 08:46:56 +02:00
dependabot[bot]
2aac99b037 build(deps): bump protobuf from 4.22.1 to 4.22.3 (#1448)
Bumps [protobuf](https://github.com/protocolbuffers/protobuf) from 4.22.1 to 4.22.3.
- [Release notes](https://github.com/protocolbuffers/protobuf/releases)
- [Changelog](https://github.com/protocolbuffers/protobuf/blob/main/generate_changelog.py)
- [Commits](https://github.com/protocolbuffers/protobuf/compare/v4.22.1...v4.22.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>
2023-04-25 06:36:06 +02:00
Moritz
2401dc785c update viv dependencies and fix (#1342)
* update dependencies and fix

* pyinstaller: add hook for new viv pas

* pyinstaller: hooks: remove duplicate entries and old analysis pass

* Update setup.py

* update hidden imports

---------

Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-04-25 06:34:40 +02:00
Moritz
f902add0ce Merge pull request #1457 from yelhamer/bugfix-symtab
SymTab _parse(): Bugfixes for the struct unpacking and for handling symtabs with a null entry size
2023-04-24 19:35:23 +02:00
Yacine Elhamer
2faae5d022 SymTab: Update unpacking format
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2023-04-24 17:57:06 +01:00
dependabot[bot]
2a2878bba0 build(deps-dev): bump ruff from 0.0.260 to 0.0.262
Bumps [ruff](https://github.com/charliermarsh/ruff) from 0.0.260 to 0.0.262.
- [Release notes](https://github.com/charliermarsh/ruff/releases)
- [Changelog](https://github.com/charliermarsh/ruff/blob/main/BREAKING_CHANGES.md)
- [Commits](https://github.com/charliermarsh/ruff/compare/v0.0.260...v0.0.262)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-24 14:58:25 +00:00
Moritz
2bb6f924cd Merge pull request #1447 from mandiant/dependabot/pip/pytest-7.3.1
build(deps-dev): bump pytest from 7.3.0 to 7.3.1
2023-04-24 12:37:38 +02:00
Yacine Elhamer
ee881ab82f code style: Fix the format of the committed code 2023-04-23 02:31:11 +01:00
Yacine Elhamer
b32a8ca510 insn.py: Get the symtab api extractor to yield FunctionName features as well 2023-04-23 01:20:25 +01:00
Yacine Elhamer
b766d957b0 insn.py: rewire symbol parsing to use SymTab instead of vivisect 2023-04-22 01:36:57 +01:00
Yacine Elhamer
e7ccea44e7 Shdr: add a constructor for vivisect's shdr representation 2023-04-22 01:33:00 +01:00
Yacine Elhamer
861e96d33e update CHANGELOG.md 2023-04-22 01:16:42 +01:00
Yacine Elhamer
07e6407115 _parse(): safeguard against zero entry size 2023-04-22 01:10:26 +01:00
Yacine Elhamer
69d44cdc16 _parse(): fix section header unpacking field size 2023-04-22 01:09:04 +01:00
Yacine Elhamer
97c8fd0525 Update CHANGELOG.md
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2023-04-21 19:36:20 +01:00
Moritz
259dfaed11 Update tests.yml 2023-04-21 17:24:06 +02:00
dependabot[bot]
bf02b2ecb4 build(deps-dev): bump pytest from 7.3.0 to 7.3.1
Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.3.0 to 7.3.1.
- [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.3.0...7.3.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-21 14:18:11 +00:00
Moritz
88c78bb411 only test binaryninja on non-forks 2023-04-21 16:15:27 +02:00
Capa Bot
2c73f08364 Sync capa-testfiles submodule 2023-04-21 14:06:49 +00:00
Capa Bot
467c19be97 Sync capa rules submodule 2023-04-19 17:01:01 +00:00
Capa Bot
96d7f20980 Sync capa rules submodule 2023-04-19 15:56:44 +00:00
Capa Bot
8965fc8a79 Sync capa rules submodule 2023-04-17 16:11:59 +00:00
Capa Bot
f4968bc1f1 Sync capa rules submodule 2023-04-17 15:59:53 +00:00
Capa Bot
fe0702a06b Sync capa-testfiles submodule 2023-04-17 15:58:44 +00:00
Yacine Elhamer
44254bfffe Update CHANGELOG.md
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-04-17 09:51:39 +01:00
Willi Ballenthin
c85050ac1a Merge pull request #1405 from ooprathamm/ruff
Linting with ruff
2023-04-17 10:46:24 +02:00
Yacine Elhamer
21f2cb6e6f Update CHANGELOG.md 2023-04-14 04:25:24 +01:00
Yacine Elhamer
c71cb55051 insn extractor: Add static api extraction using .symtab 2023-04-14 04:07:05 +01:00
Willi Ballenthin
6ba5b2b72b Merge pull request #1442 from Vector35/fix_bn_error
Check if caller.llil is None before accessing its properties
2023-04-12 14:20:51 +02:00
Xusheng
dd207fb238 Check if caller.llil is None before accessing its properties 2023-04-12 15:13:40 +08:00
Willi Ballenthin
e9e06bb571 Merge pull request #1439 from mandiant/dependabot/pip/mypy-1.2.0
build(deps-dev): bump mypy from 1.1.1 to 1.2.0
2023-04-10 20:48:47 +02:00
Willi Ballenthin
ae0e0a03a3 Merge pull request #1437 from mandiant/dependabot/pip/types-protobuf-4.22.0.2
build(deps-dev): bump types-protobuf from 4.22.0.1 to 4.22.0.2
2023-04-10 20:47:39 +02:00
Willi Ballenthin
526fc15082 Merge pull request #1436 from mandiant/dependabot/pip/pytest-7.3.0
build(deps-dev): bump pytest from 7.1.3 to 7.3.0
2023-04-10 20:46:53 +02:00
dependabot[bot]
271107436b build(deps-dev): bump mypy from 1.1.1 to 1.2.0
Bumps [mypy](https://github.com/python/mypy) from 1.1.1 to 1.2.0.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v1.1.1...v1.2.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-10 14:58:07 +00:00
dependabot[bot]
eaa4e15439 build(deps-dev): bump types-protobuf from 4.22.0.1 to 4.22.0.2
Bumps [types-protobuf](https://github.com/python/typeshed) from 4.22.0.1 to 4.22.0.2.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-10 14:57:58 +00:00
dependabot[bot]
7cfeebfff7 build(deps-dev): bump pytest from 7.1.3 to 7.3.0
Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.1.3 to 7.3.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.1.3...7.3.0)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-10 14:57:56 +00:00
Xusheng
6f3bffe689 Fix BN installation path detection does not work with Python 3.11 2023-04-10 11:45:05 +08:00
Moritz
7c4a46b7b4 update to v5.1.0 (#1429)
* update to v5.1.0

---------

Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-04-06 12:55:25 +02:00
Pratham Chauhan
efb07fafb3 fix 2023-04-05 22:16:00 +05:30
Pratham Chauhan
eedd885683 fix black 2023-04-05 17:44:57 +05:30
Pratham Chauhan
e6248cd9ed solve failing binja 2023-04-05 17:43:11 +05:30
Pratham Chauhan
3d1ef51863 revert 2023-04-05 17:33:05 +05:30
Pratham Chauhan
068ac0ca2c fix black 2023-04-05 16:29:53 +05:30
naikordian
8fe88f601f fix: Warning user to install signatures (#1420)
* fix: Warning user to install signatures

---------

Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-04-05 12:59:41 +02:00
Pratham Chauhan
eef1548baa fix capy2yara.py 2023-04-05 16:28:00 +05:30
Pratham Chauhan
6eaa46ea9a revert bninja change 2023-04-05 13:32:15 +05:30
ooprathamm
6641c8c9c9 fixing error issue
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2023-04-04 23:07:04 +05:30
Pratham Chauhan
a40126aeff reformatting with black 2023-04-04 19:10:40 +05:30
Pratham Chauhan
ccc51dab35 resolve merge conflict 2023-04-04 18:56:26 +05:30
Pratham Chauhan
89c6c235f7 resolve conflict 2023-04-04 18:46:31 +05:30
Pratham Chauhan
a260b35c9d --fix 2023-04-04 18:28:43 +05:30
Pratham Chauhan
c04774b4b1 solving unresolvable issues using --fix and ignoring some issues 2023-04-04 18:27:30 +05:30
Willi Ballenthin
d46cf5b519 Merge pull request #1427 from mandiant/dependabot/pip/types-protobuf-4.22.0.1
build(deps-dev): bump types-protobuf from 4.22.0.0 to 4.22.0.1
2023-04-04 11:21:49 +02:00
Willi Ballenthin
29682cf767 Merge pull request #1425 from mandiant/dependabot/pip/black-23.3.0
build(deps-dev): bump black from 23.1.0 to 23.3.0
2023-04-04 11:21:23 +02:00
Willi Ballenthin
42df936336 Merge pull request #1428 from mandiant/dependabot/pip/pytest-instafail-0.5.0
build(deps-dev): bump pytest-instafail from 0.4.2 to 0.5.0
2023-04-04 11:20:52 +02:00
dependabot[bot]
fe6117e87a build(deps-dev): bump pytest-instafail from 0.4.2 to 0.5.0
Bumps [pytest-instafail](https://github.com/pytest-dev/pytest-instafail) from 0.4.2 to 0.5.0.
- [Release notes](https://github.com/pytest-dev/pytest-instafail/releases)
- [Changelog](https://github.com/pytest-dev/pytest-instafail/blob/master/CHANGES.rst)
- [Commits](https://github.com/pytest-dev/pytest-instafail/compare/v0.4.2...v0.5.0)

---
updated-dependencies:
- dependency-name: pytest-instafail
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-04 07:40:27 +00:00
dependabot[bot]
04ca770545 build(deps-dev): bump black from 23.1.0 to 23.3.0
Bumps [black](https://github.com/psf/black) from 23.1.0 to 23.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/23.1.0...23.3.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-04 07:40:03 +00:00
dependabot[bot]
43f3f31d69 build(deps-dev): bump types-protobuf from 4.22.0.0 to 4.22.0.1
Bumps [types-protobuf](https://github.com/python/typeshed) from 4.22.0.0 to 4.22.0.1.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-04 07:39:46 +00:00
Willi Ballenthin
acd0020413 Merge pull request #1423 from mandiant/mypy-111
more mypy v1.1.1 fixes
2023-04-03 21:48:51 +02:00
Capa Bot
0002b05418 Sync capa rules submodule 2023-04-03 17:08:37 +00:00
Willi Ballenthin
545e198257 ci: bump more ubuntu images 2023-04-03 17:54:41 +02:00
Willi Ballenthin
d4b83e3f8a ci: pyinstaller: update to use ubuntu 20.04 for building linux
executables
2023-04-03 17:39:43 +02:00
Willi Ballenthin
efcc2e0dd4 elf: remove old print statement 2023-04-03 16:13:28 +02:00
Willi Ballenthin
5e0d6176a1 elf: parse associated strtab for symtab 2023-04-03 16:09:14 +02:00
Willi Ballenthin
e240372a90 result document: document subscope/match handling 2023-04-03 15:37:46 +02:00
Willi Ballenthin
a64a88981f tests: add another test demonstrating rd format output 2023-04-03 15:35:20 +02:00
Willi Ballenthin
bc8df09be5 result document: more deserialization 2023-04-03 15:27:48 +02:00
Willi Ballenthin
b09e3e69f2 wip: result document: deserialize into capa object instances 2023-04-03 15:04:15 +02:00
Willi Ballenthin
43128404be elf: remove old debugging code 2023-04-03 15:04:00 +02:00
Willi Ballenthin
28e85aa548 main: mypy 2023-04-03 13:48:30 +02:00
Willi Ballenthin
30c14210ed main: better separate logic for deserializing result/freeze/other 2023-04-03 13:44:19 +02:00
Willi Ballenthin
d2fc740278 result document: mypy 2023-04-03 13:44:09 +02:00
Capa Bot
cbe30199ff Sync capa-testfiles submodule 2023-04-03 11:31:24 +00:00
Willi Ballenthin
3f5d9c79f9 elf: add type hints and Symbol dataclass 2023-04-03 13:30:02 +02:00
Willi Ballenthin
59332c2e94 tests: fixtures: add paths for new ELF test file 2023-04-03 13:16:03 +02:00
Willi Ballenthin
d230780443 pep8 2023-04-03 13:00:02 +02:00
Willi Ballenthin
7387c073fb Merge pull request #1412 from manasghandat/fix-shadowed-variable
Fix shadowed variable
2023-04-03 12:58:15 +02:00
Willi Ballenthin
535ba622ae Merge pull request #1422 from yelhamer/feature-symtab-os-guess
ELF OS detection: add support for guessing that's based on .symtab entries
2023-04-03 08:41:47 +02:00
Capa Bot
c6b634f3ae Sync capa-testfiles submodule 2023-04-03 06:41:30 +00:00
Willi Ballenthin
386baec3c5 elf: hints and formatting 2023-04-03 08:40:41 +02:00
Yacine Elhamer
b2ead45ad4 tests: Add test for sample 2bf18d 2023-04-02 21:57:22 +01:00
Yacine Elhamer
74284e9dad bugfix: potential reference to uninitialized variables 2023-04-02 21:56:28 +01:00
Yacine Elhamer
270077bc73 SymTab class: update get_symbols() type and add return-value comment 2023-04-02 20:59:09 +01:00
Yacine Elhamer
367a0c483c rename the SYMTAB class to SymTab 2023-04-02 20:49:58 +01:00
Yacine Elhamer
8a272e92c7 format: removed tabs
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-04-02 20:38:44 +01:00
Yacine Elhamer
2d1105dba9 format: update elf.py to use isort and black format
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-04-02 20:36:34 +01:00
Yacine Elhamer
c798996f6e detect_elf_os(): Integrate symbol-based guessing ability 2023-04-02 18:11:11 +01:00
Yacine Elhamer
ef0e4bd4fd os-guessing: Add symtab-guessing capability 2023-04-02 18:07:46 +01:00
Yacine Elhamer
bfaee2c402 Add a class (SYMTAB) for the symbol table 2023-04-02 18:07:46 +01:00
Yacine Elhamer
1f6cd807a4 Shdr dataclass: add sh_entsize member 2023-04-02 18:07:22 +01:00
Willi Ballenthin
6f416dfefb Merge pull request #1418 from stevemk14ebr/master
Remove dynsym library name for ELF imports
2023-04-01 13:54:07 +02:00
Capa Bot
06c71a7f2b Sync capa rules submodule 2023-03-31 17:40:58 +00:00
Stephen Eckels
270350f8d1 Update CHANGELOG.md
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-03-31 13:26:41 -04:00
Stephen Eckels
c603b92bc5 Merge branch 'master' of https://github.com/stevemk14ebr/capa 2023-03-31 13:25:45 -04:00
Stephen Eckels
59be399dac Revert line removal 2023-03-31 13:25:37 -04:00
Capa Bot
7f39cb1bc3 Sync capa rules submodule 2023-03-31 14:03:51 +00:00
manasghandat
d09e1c8ee2 fix linting error 2023-03-31 12:29:26 +05:30
manasghandat
c1735b6033 Merge branch 'mandiant:master' into fix-shadowed-variable 2023-03-31 12:27:43 +05:30
Stephen Eckels
1921961cff Update todo comment to link issue
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-03-30 13:23:29 -04:00
Stephen Eckels
3cd766630f Update changelog 2023-03-30 13:21:37 -04:00
manasghandat
fac548a76e Update capa/render/proto/__init__.py
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-03-30 22:51:17 +05:30
manasghandat
24f4ebef23 Update capa/render/proto/__init__.py
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-03-30 22:51:07 +05:30
Willi Ballenthin
99ee317fd0 Merge pull request #1396 from ooprathamm/read-render
Towards improving read and rendering of results
2023-03-30 13:03:27 +02:00
Pratham Chauhan
456f6e0003 fix broken arch logic 2023-03-30 16:18:52 +05:30
Willi Ballenthin
1ccd2c4d0f tests: fix proto tests on windows (#1417)
closes  #1416
2023-03-30 11:45:03 +02:00
Willi Ballenthin
f42b5b1088 Merge pull request #1409 from mandiant/dependabot/pip/protobuf-4.22.1
build(deps): bump protobuf from 4.21.12 to 4.22.1
2023-03-30 11:17:14 +02:00
Pratham Chauhan
ed64986af8 adds a ruff.toml file for config 2023-03-30 14:22:11 +05:30
Pratham Chauhan
1b90a28acd resolved merge conflicts 2023-03-30 11:05:32 +05:30
Pratham Chauhan
cd0e0ce4d1 remove unused import 2023-03-30 10:52:05 +05:30
Pratham Chauhan
7cb4ea9273 Fix lint issues 2023-03-30 10:35:31 +05:30
Stephen Eckels
66e374a343 Update changelog 2023-03-29 16:01:31 -04:00
Stephen Eckels
5e8262d3c0 Remove dynsym from elf entirely 2023-03-29 15:58:16 -04:00
Willi Ballenthin
6bb14d0874 Merge pull request #1415 from mandiant/f-strings
use f-strings as appropriate
2023-03-29 20:47:12 +02:00
Pratham Chauhan
c3fdab8ec5 Add new test test_rdoc_to_capa 2023-03-29 22:57:11 +05:30
Pratham Chauhan
237554d84a Fix broken logic for FORMAT_FREEZE 2023-03-29 22:32:12 +05:30
Pratham Chauhan
6ed7aca5be remove rule param 2023-03-29 19:50:07 +05:30
Pratham Chauhan
a13ce094b3 use rd/test json 2023-03-29 19:41:14 +05:30
Pratham Chauhan
6806b8f5a7 use pydantic.parse_file 2023-03-29 19:02:45 +05:30
manasghandat
e3d9386239 Merge branch 'mandiant:master' into fix-shadowed-variable 2023-03-29 18:31:28 +05:30
dependabot[bot]
fbdf92367e build(deps): bump protobuf from 4.21.12 to 4.22.1
Bumps [protobuf](https://github.com/protocolbuffers/protobuf) from 4.21.12 to 4.22.1.
- [Release notes](https://github.com/protocolbuffers/protobuf/releases)
- [Changelog](https://github.com/protocolbuffers/protobuf/blob/main/generate_changelog.py)
- [Commits](https://github.com/protocolbuffers/protobuf/commits/v4.22.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-29 12:52:59 +00:00
Willi Ballenthin
2ec96d7f13 Merge pull request #1408 from mandiant/dependabot/pip/pydantic-1.10.7
build(deps): bump pydantic from 1.10.6 to 1.10.7
2023-03-29 14:52:45 +02:00
Willi Ballenthin
1c457d3428 Merge pull request #1407 from mandiant/dependabot/pip/types-protobuf-4.22.0.0
build(deps-dev): bump types-protobuf from 4.21.0.5 to 4.22.0.0
2023-03-29 14:52:14 +02:00
Pratham Chauhan
fe1193f374 removes unused imports 2023-03-29 16:12:17 +05:30
Pratham Chauhan
abbf3db2ac Revert "remove unused imports"
This reverts commit 9e12c563bc.
2023-03-29 16:11:21 +05:30
Pratham Chauhan
5a1009520d Revert "Revert "introducing match strings constant for formats""
This reverts commit b49fb7fcf9.
2023-03-29 16:10:44 +05:30
Pratham Chauhan
b49fb7fcf9 Revert "introducing match strings constant for formats"
This reverts commit 530e28cbc3.
2023-03-29 16:06:20 +05:30
Pratham Chauhan
9e12c563bc remove unused imports 2023-03-29 16:02:17 +05:30
Pratham Chauhan
530e28cbc3 introducing match strings constant for formats 2023-03-29 16:00:02 +05:30
Pratham Chauhan
637dd6bf0a Added a unit test 2023-03-29 15:51:25 +05:30
Pratham Chauhan
fdc9530352 seperating loading json and to_capa logic 2023-03-29 08:34:06 +05:30
manasghandat
4990f7a2c8 Fix requested changes 2023-03-28 22:11:37 +05:30
Capa Bot
b5f274bf56 Sync capa rules submodule 2023-03-28 14:07:51 +00:00
Willi Ballenthin
ac2d01a60a use f-strings as appropriate
closes #600
2023-03-28 11:43:49 +02:00
Willi Ballenthin
95bdaf072b Merge pull request #1399 from ggold7046/patch-15
Update utils.py
2023-03-28 09:47:11 +02:00
Capa Bot
af1500825a Sync capa rules submodule 2023-03-28 07:20:10 +00:00
AG
cd2ef15a8a Update CHANGELOG.md
Update changelog to reflect changes introduced in pull request #1399
2023-03-28 01:11:23 +05:30
Pratham Chauhan
02359e5e84 fix 2023-03-27 22:22:25 +05:30
dependabot[bot]
d873cc0257 build(deps): bump pydantic from 1.10.6 to 1.10.7
Bumps [pydantic](https://github.com/pydantic/pydantic) from 1.10.6 to 1.10.7.
- [Release notes](https://github.com/pydantic/pydantic/releases)
- [Changelog](https://github.com/pydantic/pydantic/blob/v1.10.7/HISTORY.md)
- [Commits](https://github.com/pydantic/pydantic/compare/v1.10.6...v1.10.7)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-27 14:09:09 +00:00
dependabot[bot]
ea2acea668 build(deps-dev): bump types-protobuf from 4.21.0.5 to 4.22.0.0
Bumps [types-protobuf](https://github.com/python/typeshed) from 4.21.0.5 to 4.22.0.0.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-27 14:08:45 +00:00
Pratham Chauhan
84052c3ac5 init 2023-03-27 19:21:55 +05:30
Willi Ballenthin
4a40732cad Merge pull request #1406 from mandiant/williballenthin-patch-1
ci: tests: run binja after code style/linter
2023-03-27 13:17:47 +02:00
Willi Ballenthin
cd9f32ced5 Merge pull request #1398 from mandiant/fix-shadowed-variable
main: fix variable shadowing module os
2023-03-27 13:17:32 +02:00
Willi Ballenthin
2bedc6b181 ci: tests: run binja after code style/linter 2023-03-27 11:47:53 +02:00
Pratham Chauhan
e26deb472e Update CHANGELOG.md 2023-03-26 22:54:12 +05:30
Pratham Chauhan
78d0111a6c Final changes 2023-03-26 22:09:04 +05:30
Capa Bot
d61c85c171 Sync capa rules submodule 2023-03-26 09:29:01 +00:00
Pratham Chauhan
03f0034d33 working meta parsing 2023-03-25 14:47:59 +05:30
manasghandat
3f2e698684 fix mypy issue 2023-03-24 22:20:37 +05:30
manasghandat
259aa53de4 Merge branch 'fix-shadowed-variable' of https://github.com/mandiant/capa into fix-shadowed-variable 2023-03-24 21:11:39 +05:30
manasghandat
7915fb3fb6 Merge branch 'master' of https://github.com/mandiant/capa 2023-03-24 21:06:41 +05:30
AG
fbb348bc82 Update utils.py
Changed the colour/highlight to "cyan" instead of "blue" for easy noticing.
2023-03-24 20:50:45 +05:30
Willi Ballenthin
a8552e6b96 Merge pull request #1316 from mandiant/wb-proto
protobuf support
2023-03-24 11:51:56 +01:00
Willi Ballenthin
4be3fe1628 Merge branch 'master' into wb-proto 2023-03-24 11:51:45 +01:00
Willi Ballenthin
a087045322 Merge pull request #1387 from manasghandat/main
Fix mypy update 1.1.1 by dependabot
2023-03-24 11:51:01 +01:00
Pratham Chauhan
248229a383 Functioning parse_raw 2023-03-24 10:29:37 +05:30
Pratham Chauhan
0ff22d319f fix 2023-03-24 01:22:29 +05:30
manasghandat
a1dfcc73dd fix basicblockfeature 2023-03-23 21:20:06 +05:30
Willi Ballenthin
3e98115dc2 main: fix variable shadowing module os 2023-03-23 16:11:21 +01:00
Willi Ballenthin
ddc52fa21c Merge branch 'master' of personal.github.com:mandiant/capa 2023-03-23 16:04:54 +01:00
xusheng
986e2e6057 Merge pull request #1 from mandiant/binja-ci 2023-03-24 18:39:12 +08:00
Capa Bot
793057c202 Sync capa-testfiles submodule 2023-03-24 09:30:40 +00:00
Capa Bot
3bf9cacaec Sync capa rules submodule 2023-03-24 08:55:50 +00:00
Capa Bot
bed4593d04 Sync capa-testfiles submodule 2023-03-23 18:29:19 +00:00
Willi Ballenthin
e8082173ad tests: add test demonstrating to/from proto scripts 2023-03-23 15:42:43 +01:00
Willi Ballenthin
b1f4035530 Merge branch 'wb-proto' of personal.github.com:mandiant/capa into wb-proto 2023-03-23 15:30:10 +01:00
Willi Ballenthin
0d4a92a351 gitignore 2023-03-23 15:27:32 +01:00
Willi Ballenthin
89803e7523 ci: add binary ninja installation and test invocation 2023-03-23 14:17:26 +01:00
Willi Ballenthin
613ce92cfd tests: remove old debugging statements 2023-03-23 14:14:04 +01:00
Willi Ballenthin
8bde277be2 ci: binja: update installer to use root 2023-03-23 14:11:48 +01:00
Willi Ballenthin
3be7bbbf88 ci: binja: log more 2023-03-23 14:06:36 +01:00
Willi Ballenthin
d8aa276f25 tests: debug binja api 2023-03-23 14:04:14 +01:00
Willi Ballenthin
dcddef09dc ci: binja: inject secrets 2023-03-23 14:00:28 +01:00
Willi Ballenthin
ad442aaae3 ci: binja: fix curl output 2023-03-23 13:58:04 +01:00
Willi Ballenthin
21ecc7618a ci: binja: fix curl 2023-03-23 13:56:08 +01:00
Willi Ballenthin
8f8a0b118f ci: add test workflow for binja testing 2023-03-23 13:52:58 +01:00
Pratham Chauhan
0358b46fcd add FORMAT_RESULT 2023-03-23 18:07:03 +05:30
Willi Ballenthin
1a29077b45 tests: binja: don't crash on bad license - log instead 2023-03-23 12:38:52 +01:00
Willi Ballenthin
c249b841e8 tests: binja: ensure the license is valid 2023-03-23 12:37:06 +01:00
Willi Ballenthin
7d12942cf7 Merge branch 'binja_backend' of github.com:Vector35/capa into Vector35-binja_backend 2023-03-23 11:31:25 +01:00
Willi Ballenthin
c52b0a22e0 tests: simplify loading of result document from file 2023-03-23 11:04:53 +01:00
Willi Ballenthin
840145f947 Update CHANGELOG.md 2023-03-23 11:02:58 +01:00
Willi Ballenthin
10d6e55d62 proto: remove main entrypoint 2023-03-23 10:58:51 +01:00
Willi Ballenthin
80112bac64 add scripts showing conversion to/from protobuf format 2023-03-23 10:58:22 +01:00
Willi Ballenthin
49ff9d5a7c pep8 2023-03-23 10:58:13 +01:00
Willi Ballenthin
1044709803 tests: proto: test byte representation, not messages 2023-03-23 10:57:35 +01:00
Willi Ballenthin
252f5cebb7 proto: remove old code 2023-03-23 10:35:41 +01:00
Willi Ballenthin
e8ddee4782 Merge branch 'master' of personal.github.com:mandiant/capa into wb-proto 2023-03-23 10:35:30 +01:00
Willi Ballenthin
8daa1c032c Merge pull request #1350 from captainGeech42/issues/1348
feature: support for OS override
2023-03-23 10:32:39 +01:00
Willi Ballenthin
beccf28d09 Merge branch 'rd-hardening' into wb-proto 2023-03-23 10:31:29 +01:00
Willi Ballenthin
5ac3414490 Merge pull request #1395 from HongThatCong/master
Update __init__.py
2023-03-23 10:31:14 +01:00
Willi Ballenthin
5d49f5a1d2 Merge branch 'master' of personal.github.com:mandiant/capa into wb-proto 2023-03-23 10:30:07 +01:00
Capa Bot
41bf5f0926 Sync capa-testfiles submodule 2023-03-23 09:29:26 +00:00
Capa Bot
4c5a16a1db Sync capa rules submodule 2023-03-23 07:49:17 +00:00
Capa Bot
85fb9aa99f Sync capa rules submodule 2023-03-23 07:48:11 +00:00
Capa Bot
57d34087dd Sync capa-testfiles submodule 2023-03-22 19:50:38 +00:00
Capa Bot
2d65b4b2a1 Sync capa rules submodule 2023-03-22 19:43:40 +00:00
Willi Ballenthin
d068faa35e tests: remove old comment 2023-03-22 13:24:42 +01:00
Willi Ballenthin
1c33cd4470 pep8 2023-03-22 13:12:22 +01:00
Willi Ballenthin
21e410cc77 proto: implement deserialization from protobuf format 2023-03-22 13:08:10 +01:00
Willi Ballenthin
68ebd87127 tests: proto: fix property name 2023-03-22 11:22:12 +01:00
Willi Ballenthin
62069e9e59 tests: proto: fix module references 2023-03-22 11:21:59 +01:00
Willi Ballenthin
14a2088606 proto: move impl to top level module 2023-03-22 11:16:37 +01:00
Willi Ballenthin
114c3854e7 tests: add round trip tests for proto 2023-03-22 11:15:50 +01:00
Willi Ballenthin
26ca593fad proto: sketch from pb2 routines 2023-03-22 11:15:34 +01:00
Willi Ballenthin
ec785f9d6d proto: don't use name property due to top level python decorator name 2023-03-22 11:03:18 +01:00
Willi Ballenthin
f54ef35a7a mypy 2023-03-22 10:58:24 +01:00
Willi Ballenthin
e0b57fc74e insn: fix type annotation for operand index 2023-03-22 10:57:17 +01:00
Willi Ballenthin
4754a84a8a pep8 2023-03-22 10:52:40 +01:00
Willi Ballenthin
02fdf41969 tests: add tests demonstrating result document round tripping 2023-03-22 10:47:45 +01:00
Willi Ballenthin
92e75ee89b insn: document ranges of numbers and offsets 2023-03-22 10:09:57 +01:00
Willi Ballenthin
7c2b6a3161 proto: update generate pb2 2023-03-22 10:00:51 +01:00
Willi Ballenthin
26a8647444 proto: revert address field name change 2023-03-22 10:00:12 +01:00
Willi Ballenthin
cae7c4d0a7 proto: update doc and field numbers 2023-03-22 09:58:03 +01:00
Willi Ballenthin
27a5e17a3e proto: rename address value field 2023-03-22 09:52:01 +01:00
Willi Ballenthin
a9ba133506 bulk-process: fix some variable references 2023-03-22 09:48:20 +01:00
Willi Ballenthin
eb20724d78 Merge branch 'master' into wb-proto 2023-03-22 09:46:03 +01:00
Willi Ballenthin
1b9e486c49 Merge pull request #1351 from mandiant/wb-mr-proto
WIP: proto translation
2023-03-22 09:44:59 +01:00
Willi Ballenthin
7ef167fcd0 Update scripts/bulk-process.py
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2023-03-22 09:44:00 +01:00
Hồng Thất Công
9db106e3f0 Update __init__.py
Update IDA plugin
2023-03-22 11:58:46 +07:00
manasghandat
b4052e5a64 Add appropriate comments 2023-03-22 07:49:20 +05:30
manasghandat
9a77f18ced Add appropriate comments 2023-03-22 07:45:59 +05:30
Capa Bot
03996f2b82 Sync capa rules submodule 2023-03-21 21:04:25 +00:00
Willi Ballenthin
53ca96fcee result document: make all classes frozen and forbid extra attributes 2023-03-21 17:37:27 +01:00
Willi Ballenthin
c1ca4ab703 isort 2023-03-21 17:22:43 +01:00
Willi Ballenthin
43bcf401b2 bulk-process: reference error 2023-03-21 16:57:16 +01:00
Willi Ballenthin
f1c495dc0a *: use FORMAT_AUTO instead of string literal 2023-03-21 16:54:48 +01:00
Willi Ballenthin
98eb28704c main: don't embed format/os overrides in metadata 2023-03-21 16:47:11 +01:00
Willi Ballenthin
1f3582c9c3 mypy 2023-03-21 16:45:24 +01:00
Willi Ballenthin
62f7bddd4d Merge pull request #1389 from ggold7046/patch-16
Update view.py
2023-03-21 16:31:05 +01:00
AG
b097569607 Update view.py
Updated with f string for better readability.
2023-03-21 19:53:10 +05:30
manasghandat
da6f72c20a fix mypy fails 2023-03-21 19:10:11 +05:30
manasghandat
00e94d976a fix linting issue 2023-03-21 18:51:51 +05:30
manasghandat
d1d6db877d Merge branch 'mandiant:master' into main 2023-03-21 18:47:16 +05:30
manasghandat
da3e3c6bb4 fix mypy fails 2023-03-21 18:46:22 +05:30
Willi Ballenthin
e57be09823 Merge branch 'issues/1348' of github.com:captainGeech42/capa into issues/1348 2023-03-21 14:04:46 +01:00
Willi Ballenthin
7598a97888 Merge branch 'master' of personal.github.com:mandiant/capa into pr-1350 2023-03-21 14:02:02 +01:00
Willi Ballenthin
ebaf51ce56 Merge branch 'master' into issues/1348 2023-03-21 13:54:52 +01:00
Willi Ballenthin
0cf8b154a4 pep8 2023-03-21 13:53:59 +01:00
Willi Ballenthin
b420d6bbb2 Merge pull request #1386 from mandiant/dependabot/pip/pyinstaller-5.9.0
build(deps-dev): bump pyinstaller from 5.8.0 to 5.9.0
2023-03-21 13:04:57 +01:00
mr-tz
6086cc5e18 update number/offset understanding 2023-03-20 18:11:24 +01:00
mr-tz
c3ed12d8d4 add helper function 2023-03-20 17:46:36 +01:00
mr-tz
2d98c9e3c4 address mypy warnings 2023-03-20 17:45:55 +01:00
mr-tz
0933040d0b remove protobuf from rd scheme generation test 2023-03-20 17:45:23 +01:00
mr-tz
12046e698e don't change child data 2023-03-20 17:43:21 +01:00
mr-tz
73ac83bd06 reformat changelog 2023-03-20 16:58:06 +01:00
mr-tz
631685472d add assert_never 2023-03-20 16:55:42 +01:00
mr-tz
32bcf999b8 remove proto from pydantic generation code 2023-03-20 16:53:44 +01:00
dependabot[bot]
008f6d1839 build(deps-dev): bump pyinstaller from 5.8.0 to 5.9.0
Bumps [pyinstaller](https://github.com/pyinstaller/pyinstaller) from 5.8.0 to 5.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/v5.8.0...v5.9.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-20 14:58:43 +00:00
dependabot[bot]
1746a640cc build(deps): bump pydantic from 1.10.5 to 1.10.6 (#1380)
Bumps [pydantic](https://github.com/pydantic/pydantic) from 1.10.5 to 1.10.6.
- [Release notes](https://github.com/pydantic/pydantic/releases)
- [Changelog](https://github.com/pydantic/pydantic/blob/v1.10.6/HISTORY.md)
- [Commits](https://github.com/pydantic/pydantic/compare/v1.10.5...v1.10.6)

---
updated-dependencies:
- dependency-name: pydantic
  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-03-17 14:12:20 +01:00
Capa Bot
d5937e4af5 Sync capa rules submodule 2023-03-16 17:41:19 +00:00
manasghandat
1336796c0c code style : update remaining files (#1353)
* code style: update string formatting using fstrings

---------

Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2023-03-16 11:16:18 +01:00
manasghandat
2efcfcf239 fix merge conflicts 2023-03-15 07:19:41 +05:30
manasghandat
8f2ffe8526 fix code style 2023-03-15 07:08:31 +05:30
Capa Bot
8cf74759a6 Sync capa rules submodule 2023-03-14 18:35:45 +00:00
Capa Bot
22a1a8e41f Sync capa rules submodule 2023-03-14 18:30:53 +00:00
Harsh Mehta
74009eb4a4 Updated Copyright (#1383)
* Updated Copyright
2023-03-14 17:58:43 +01:00
manasghandat
5932358f9d fix changes 2023-03-14 22:10:02 +05:30
manasghandat
1ad5364fec fix changes 2023-03-14 22:09:35 +05:30
Capa Bot
201330295c Sync capa rules submodule 2023-03-14 16:25:56 +00:00
mr-tz
a7b7f643a5 update translator and tests 2023-03-14 10:13:49 +01:00
Capa Bot
4fd6f17ced Sync capa rules submodule 2023-03-14 07:34:15 +00:00
dependabot[bot]
e67679658a build(deps-dev): bump mypy from 1.0.1 to 1.1.1
Bumps [mypy](https://github.com/python/mypy) from 1.0.1 to 1.1.1.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v1.0.1...v1.1.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-13 14:58:43 +00:00
manasghandat
d67f924b73 Merge branch 'master' of https://github.com/mandiant/capa 2023-03-12 17:41:45 +05:30
Willi Ballenthin
961daf6c36 Merge pull request #1366 from ggold7046/patch-1
Update profile-memory.py
2023-03-11 13:14:09 +01:00
Willi Ballenthin
748e7641ef Merge pull request #1367 from ggold7046/patch-3
Update match-function-id.py
2023-03-11 13:13:27 +01:00
AG
6321adc411 Update match-function-id.py
Updated with f string for enhanced readability.
2023-03-11 12:43:22 +05:30
AG
02e451a2b1 Update profile-memory.py
Updated with f string for enhanced readability.
2023-03-11 12:29:59 +05:30
Willi Ballenthin
8cac47038c Merge pull request #1354 from ggold7046/patch-1
Update import-to-bn.py
2023-03-10 17:18:21 +01:00
Willi Ballenthin
59ab8e0b04 Merge pull request #1356 from ggold7046/patch-3
Update import-to-ida.py
2023-03-10 17:17:59 +01:00
Willi Ballenthin
577d96c026 Merge pull request #1365 from linpeiyu164/master
fix wrong indentation level for args.backend
2023-03-10 17:17:22 +01:00
linpeiyu164
7031c68a85 fix wrong indentation level for args.backend 2023-03-11 00:07:24 +08:00
Willi Ballenthin
3a7326726e Merge pull request #1357 from ggold7046/patch-4
Update insn.py
2023-03-10 10:04:29 +01:00
Willi Ballenthin
f01d79df46 Merge pull request #1358 from ggold7046/patch-5
Update file.py
2023-03-10 10:04:00 +01:00
AG
df6de3446c Update file.py
Updated with f string for enhanced readability.
2023-03-10 13:10:02 +05:30
AG
eaeef59583 Update insn.py
Updated with f strings for enhanced readability.
2023-03-10 13:03:04 +05:30
manasghandat
f9c7ca2941 fix CI issue in tests 2023-03-10 10:34:17 +05:30
AG
50935372ca Update import-to-ida.py
Updated with f string for enhanced readability.
2023-03-10 01:36:17 +05:30
AG
d8f89d49d4 Update import-to-bn.py
Used f string for enhanced readability.
2023-03-10 01:17:59 +05:30
Zander Work
7e823057b9 Apply suggestions from code review
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-03-09 11:51:19 -05:00
manasghandat
e4d69984d3 Merge branch 'fstring' of https://github.com/manasghandat/capa into fstring 2023-03-09 22:04:13 +05:30
manasghandat
acd04e7181 Merge branch 'mandiant:master' into fstring 2023-03-09 22:03:42 +05:30
manasghandat
22a53bb1dc fix as per review 2023-03-09 22:01:52 +05:30
manasghandat
aaef16f51b Merge branch 'master' of https://github.com/manasghandat/capa into fstring 2023-03-09 22:00:37 +05:30
manasghandat
8613c88a60 update according to review 2023-03-09 21:59:16 +05:30
manasghandat
6070bd562e Update scripts/import-to-ida.py
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-03-09 21:21:14 +05:30
Willi Ballenthin
01c4ac822c Merge pull request #1344 from mandiant/fix/1333
explorer: improve embedded PE detection
2023-03-09 15:49:10 +01:00
manasghandat
05dbdd4473 code style: add fstrings 2023-03-09 17:19:34 +05:30
Xusheng
64323b394a Encode the path with utf8 and then convert to hex in find_binja_path 2023-03-09 16:32:21 +08:00
Xusheng
70f6f1cd03 Use the binja extractor to get functions/basic blocks/instructions when the feature extractor is executed alone 2023-03-09 16:01:51 +08:00
Xusheng
e9d4a23dad Do MLIL basic block look-up in get_basic_blocks to avoid a O(n^2) algorithm 2023-03-09 15:53:44 +08:00
mr-tz
3cdbc66375 refactor 2023-03-09 07:40:58 +01:00
manasghandat
5128638071 code style: update lint.py (#1352)
* code style: update lint.py
2023-03-09 07:28:47 +01:00
manasghandat
1f80791f8f code style: update lint.py with correct format 2023-03-08 21:19:14 +05:30
mr-tz
44d8e693b0 improve int/Integer handling 2023-03-08 16:06:57 +01:00
manasghandat
3bdc61f5ee code style: update lint.py 2023-03-08 20:02:33 +05:30
mr-tz
a7e4d265e2 convert rd meta to proto 2023-03-08 14:45:26 +01:00
Willi Ballenthin
0ac497ab59 Merge pull request #1346 from mandiant/dependabot/pip/tqdm-4.65.0
build(deps): bump tqdm from 4.64.1 to 4.65.0
2023-03-08 14:35:46 +01:00
Zander Work
dbb0200147 update changelog 2023-03-07 00:20:19 -05:00
Zander Work
ff7a93f364 show overriden format/os in output 2023-03-07 00:15:42 -05:00
Zander Work
8f6a660f3d initial support for os override 2023-03-07 00:11:33 -05:00
Xusheng
64c542502b Fix the placement of some imports 2023-03-07 11:30:35 +08:00
Xusheng
b4974a80bb Fix typo in OS name 2023-03-07 11:06:18 +08:00
Mike Hunhoff
95f23dafe5 Update CHANGELOG.md 2023-03-06 08:55:32 -07:00
Mike Hunhoff
02dc42154b Update CHANGELOG.md
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-03-06 08:53:57 -07:00
dependabot[bot]
4047780c08 build(deps): bump tqdm from 4.64.1 to 4.65.0
Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.64.1 to 4.65.0.
- [Release notes](https://github.com/tqdm/tqdm/releases)
- [Commits](https://github.com/tqdm/tqdm/compare/v4.64.1...v4.65.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-06 14:59:16 +00:00
Xusheng
c648af2cb4 Select a different test file for the nzxor feature 2023-03-05 12:52:49 +08:00
Xusheng
4a698ffdff Add a Binary Ninja backend for capa 2023-03-05 12:52:49 +08:00
Xusheng
1babdb069f Update readme for generating rule cache 2023-03-04 18:46:36 +08:00
Xusheng
b49213bef6 Include the type of value when the value of a Number is unexpected 2023-03-04 18:46:36 +08:00
Xusheng
42e877671b Update gitignore for pipfile and cache folder 2023-03-04 18:46:36 +08:00
Mike Hunhoff
14c18727db update CHANGELOG 2023-03-03 09:55:45 -07:00
Mike Hunhoff
aacfcaaa23 explorer: improve embedded PE detection 2023-03-03 09:52:50 -07:00
Mike Hunhoff
9f3428e1c3 explorer: fix plugin exception when loaded under idat (#1341) 2023-03-02 13:42:43 -07:00
Moritz
52de09a032 Fix byte/string extraction and unit tests (#1339)
* Fix wrong expected results on string and bytes tests. Fix https://github.com/mandiant/capa/issues/1336

* Fix IDA insn/byte extractor checks wrong address. Fix https://github.com/mandiant/capa/issues/1327

* fix vivisect string check and tests

---------

Co-authored-by: Xusheng <xusheng@vector35.com>
2023-03-02 10:33:14 +01:00
Capa Bot
be6bb879f3 Sync capa rules submodule 2023-03-01 15:50:20 +00:00
Capa Bot
f7371c4a9f Sync capa rules submodule 2023-03-01 15:09:07 +00:00
Capa Bot
bd7cf8cdd1 Sync capa rules submodule 2023-02-28 10:41:07 +00:00
Willi Ballenthin
70b39cbd2c Merge pull request #1328 from mandiant/dependabot/pip/types-tabulate-0.9.0.1
build(deps-dev): bump types-tabulate from 0.9.0.0 to 0.9.0.1
2023-02-28 10:50:37 +01:00
dependabot[bot]
199a5cff4b build(deps-dev): bump types-tabulate from 0.9.0.0 to 0.9.0.1
Bumps [types-tabulate](https://github.com/python/typeshed) from 0.9.0.0 to 0.9.0.1.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-27 14:59:14 +00:00
Capa Bot
501e213dce Sync capa rules submodule 2023-02-27 08:59:54 +00:00
Capa Bot
d663007e60 Sync capa rules submodule 2023-02-24 14:52:58 +00:00
Mike Hunhoff
a07ca443f0 update OS to match OS_ANY for all supported OSes (#1324) 2023-02-24 07:51:40 -07:00
Willi Ballenthin
84df8baa5f Merge pull request #1313 from mandiant/dependabot/pip/pyinstaller-5.8.0
build(deps-dev): bump pyinstaller from 5.7.0 to 5.8.0
2023-02-24 10:26:09 +01:00
Willi Ballenthin
241c0aeedd Merge pull request #1321 from mandiant/dependabot/pip/mypy-1.0.1
build(deps-dev): bump mypy from 0.991 to 1.0.1
2023-02-24 10:24:39 +01:00
Willi Ballenthin
ae85399193 Merge pull request #1320 from mandiant/dependabot/pip/pydantic-1.10.5
build(deps): bump pydantic from 1.10.4 to 1.10.5
2023-02-24 10:24:14 +01:00
Capa Bot
17f70bb87c Sync capa rules submodule 2023-02-23 08:47:24 +00:00
Capa Bot
7a1f2f4b3b Sync capa rules submodule 2023-02-22 19:24:48 +00:00
Capa Bot
599d3ac92c Sync capa rules submodule 2023-02-21 21:38:32 +00:00
Capa Bot
02f8e57e66 Sync capa rules submodule 2023-02-21 10:46:20 +00:00
dependabot[bot]
b6ac6d2959 build(deps-dev): bump mypy from 0.991 to 1.0.1
Bumps [mypy](https://github.com/python/mypy) from 0.991 to 1.0.1.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v0.991...v1.0.1)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-20 14:58:39 +00:00
dependabot[bot]
c681175685 build(deps): bump pydantic from 1.10.4 to 1.10.5
Bumps [pydantic](https://github.com/pydantic/pydantic) from 1.10.4 to 1.10.5.
- [Release notes](https://github.com/pydantic/pydantic/releases)
- [Changelog](https://github.com/pydantic/pydantic/blob/v1.10.5/HISTORY.md)
- [Commits](https://github.com/pydantic/pydantic/compare/v1.10.4...v1.10.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-20 14:58:27 +00:00
Capa Bot
5e600d02a8 Sync capa rules submodule 2023-02-20 08:05:09 +00:00
Capa Bot
b9edb6dbc9 Sync capa-testfiles submodule 2023-02-16 10:31:51 +00:00
Capa Bot
6e5302e5ec Sync capa rules submodule 2023-02-15 16:46:14 +00:00
Capa Bot
4b472c8564 Sync capa rules submodule 2023-02-15 15:16:41 +00:00
Capa Bot
4ccf6f0e69 Sync capa rules submodule 2023-02-15 10:57:23 +00:00
Capa Bot
eac3d8336d Sync capa-testfiles submodule 2023-02-15 10:56:23 +00:00
Capa Bot
53475c9643 Sync capa rules submodule 2023-02-15 10:55:49 +00:00
Willi Ballenthin
3c0361fd5c Merge pull request #1317 from mandiant/fix-loop-viv
fix loop detection corner case
2023-02-15 11:50:26 +01:00
mr-tz
0d14c168a4 fix loop detection corner case 2023-02-15 11:41:54 +01:00
Capa Bot
00ecfe7a80 Sync capa-testfiles submodule 2023-02-15 10:22:12 +00:00
dependabot[bot]
e8cef536f6 build(deps-dev): bump pyinstaller from 5.7.0 to 5.8.0
Bumps [pyinstaller](https://github.com/pyinstaller/pyinstaller) from 5.7.0 to 5.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/v5.7.0...v5.8.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-13 14:59:26 +00:00
198 changed files with 48351 additions and 4328 deletions

View File

@@ -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

@@ -159,12 +159,25 @@ The process described here has several goals:
Please follow these steps to have your contribution considered by the maintainers:
0. Sign the [Contributor License Agreement](#contributor-license-agreement)
1. Follow the [styleguides](#styleguides)
2. Update the CHANGELOG and add tests and documentation. In case they are not needed, indicate it in [the PR template](pull_request_template.md).
3. After you submit your pull request, verify that all [status checks](https://help.github.com/articles/about-status-checks/) are passing <details><summary>What if the status checks are failing? </summary>If a status check is failing, and you believe that the failure is unrelated to your change, please leave a comment on the pull request explaining why you believe the failure is unrelated. A maintainer will re-run the status check for you. If we conclude that the failure was a false positive, then we will open an issue to track that problem with our status check suite.</details>
While the prerequisites above must be satisfied prior to having your pull request reviewed, the reviewer(s) may ask you to complete additional design work, tests, or other changes before your pull request can be ultimately accepted.
### Contributor License Agreement
Contributions to this project must be accompanied by a Contributor License
Agreement. You (or your employer) retain the copyright to your contribution,
this simply gives us permission to use and redistribute your contributions as
part of the project. Head over to <https://cla.developers.google.com/> to see
your current agreements on file or to sign a new one.
You generally only need to submit a CLA once, so if you've already submitted one
(even if it was for a different project), you probably don't need to do it
again.
## Styleguides
### Git Commit Messages

41
.github/flake8.ini vendored Normal file
View File

@@ -0,0 +1,41 @@
[flake8]
max-line-length = 120
extend-ignore =
# E203: whitespace before ':' (black does this)
E203,
# F401: `foo` imported but unused (prefer ruff)
F401,
# F811 Redefinition of unused `foo` (prefer ruff)
F811,
# E501 line too long (prefer black)
E501,
# B010 Do not call setattr with a constant attribute value
B010,
# G200 Logging statement uses exception in arguments
G200,
# SIM102 Use a single if-statement instead of nested if-statements
# doesn't provide a space for commenting or logical separation of conditions
SIM102,
# SIM114 Use logical or and a single body
# makes logic trees too complex
SIM114,
# SIM117 Use 'with Foo, Bar:' instead of multiple with statements
# makes lines too long
SIM117
per-file-ignores =
# T201 print found.
#
# scripts are meant to print output
scripts/*: T201
# capa.exe is meant to print output
capa/main.py: T201
# IDA tests emit results to output window so need to print
tests/test_ida_features.py: T201
# utility used to find the Binary Ninja API via invoking python.exe
capa/features/extractors/binja/find_binja_api.py: T201
copyright-check = True
copyright-min-file-size = 1
copyright-regexp = Copyright \(C\) 2023 Mandiant, Inc. All Rights Reserved.

View File

@@ -42,6 +42,9 @@ ignore_missing_imports = True
[mypy-idautils.*]
ignore_missing_imports = True
[mypy-ida_auto.*]
ignore_missing_imports = True
[mypy-ida_bytes.*]
ignore_missing_imports = True
@@ -83,3 +86,6 @@ ignore_missing_imports = True
[mypy-netnode.*]
ignore_missing_imports = True
[mypy-ghidra.*]
ignore_missing_imports = True

View File

@@ -38,39 +38,36 @@ hiddenimports = [
"vivisect",
"vivisect.analysis",
"vivisect.analysis.amd64",
"vivisect.analysis.amd64",
"vivisect.analysis.amd64.emulation",
"vivisect.analysis.amd64.golang",
"vivisect.analysis.crypto",
"vivisect.analysis.crypto",
"vivisect.analysis.crypto.constants",
"vivisect.analysis.elf",
"vivisect.analysis.elf.elfplt",
"vivisect.analysis.elf.elfplt_late",
"vivisect.analysis.elf.libc_start_main",
"vivisect.analysis.generic",
"vivisect.analysis.generic",
"vivisect.analysis.generic.codeblocks",
"vivisect.analysis.generic.emucode",
"vivisect.analysis.generic.entrypoints",
"vivisect.analysis.generic.funcentries",
"vivisect.analysis.generic.impapi",
"vivisect.analysis.generic.linker",
"vivisect.analysis.generic.mkpointers",
"vivisect.analysis.generic.noret",
"vivisect.analysis.generic.pointers",
"vivisect.analysis.generic.pointertables",
"vivisect.analysis.generic.relocations",
"vivisect.analysis.generic.strconst",
"vivisect.analysis.generic.switchcase",
"vivisect.analysis.generic.symswitchcase",
"vivisect.analysis.generic.thunks",
"vivisect.analysis.generic.noret",
"vivisect.analysis.i386",
"vivisect.analysis.i386",
"vivisect.analysis.i386.calling",
"vivisect.analysis.i386.golang",
"vivisect.analysis.i386.importcalls",
"vivisect.analysis.i386.instrhook",
"vivisect.analysis.i386.thunk_bx",
"vivisect.analysis.ms",
"vivisect.analysis.i386.thunk_reg",
"vivisect.analysis.ms",
"vivisect.analysis.ms.hotpatch",
"vivisect.analysis.ms.localhints",
@@ -81,8 +78,40 @@ hiddenimports = [
"vivisect.impapi.posix.amd64",
"vivisect.impapi.posix.i386",
"vivisect.impapi.windows",
"vivisect.impapi.windows.advapi_32",
"vivisect.impapi.windows.advapi_64",
"vivisect.impapi.windows.amd64",
"vivisect.impapi.windows.gdi_32",
"vivisect.impapi.windows.gdi_64",
"vivisect.impapi.windows.i386",
"vivisect.impapi.windows.kernel_32",
"vivisect.impapi.windows.kernel_64",
"vivisect.impapi.windows.msvcr100_32",
"vivisect.impapi.windows.msvcr100_64",
"vivisect.impapi.windows.msvcr110_32",
"vivisect.impapi.windows.msvcr110_64",
"vivisect.impapi.windows.msvcr120_32",
"vivisect.impapi.windows.msvcr120_64",
"vivisect.impapi.windows.msvcr71_32",
"vivisect.impapi.windows.msvcr80_32",
"vivisect.impapi.windows.msvcr80_64",
"vivisect.impapi.windows.msvcr90_32",
"vivisect.impapi.windows.msvcr90_64",
"vivisect.impapi.windows.msvcrt_32",
"vivisect.impapi.windows.msvcrt_64",
"vivisect.impapi.windows.ntdll_32",
"vivisect.impapi.windows.ntdll_64",
"vivisect.impapi.windows.ole_32",
"vivisect.impapi.windows.ole_64",
"vivisect.impapi.windows.rpcrt4_32",
"vivisect.impapi.windows.rpcrt4_64",
"vivisect.impapi.windows.shell_32",
"vivisect.impapi.windows.shell_64",
"vivisect.impapi.windows.user_32",
"vivisect.impapi.windows.user_64",
"vivisect.impapi.windows.ws2plus_32",
"vivisect.impapi.windows.ws2plus_64",
"vivisect.impapi.winkern",
"vivisect.impapi.winkern.i386",
"vivisect.impapi.winkern.amd64",
"vivisect.parsers.blob",

View File

@@ -18,7 +18,7 @@ a = Analysis(
# this gets invoked from the directory of the spec file,
# i.e. ./.github/pyinstaller
("../../rules", "rules"),
("../../sigs", "sigs"),
("../../capa/sigs", "sigs"),
("../../cache", "cache"),
# capa.render.default uses tabulate that depends on wcwidth.
# it seems wcwidth uses a json file `version.json`
@@ -61,6 +61,7 @@ a = Analysis(
"qt5",
"pyqtwebengine",
"pyasn1",
"binaryninja",
],
)
@@ -78,7 +79,7 @@ exe = EXE(
name="capa",
icon="logo.ico",
debug=False,
strip=None,
strip=False,
upx=True,
console=True,
)

43
.github/ruff.toml vendored Normal file
View File

@@ -0,0 +1,43 @@
# 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"]
# Allow autofix for all enabled rules (when `--fix`) is provided.
fixable = ["ALL"]
unfixable = []
# E402 module level import not at top of file
# E722 do not use bare 'except'
# E501 line too long
ignore = ["E402", "E722", "E501"]
line-length = 120
exclude = [
# Exclude a variety of commonly ignored directories.
".bzr",
".direnv",
".eggs",
".git",
".git-rewrite",
".hg",
".mypy_cache",
".nox",
".pants.d",
".pytype",
".ruff_cache",
".svn",
".tox",
".venv",
"__pypackages__",
"_build",
"buck-out",
"build",
"dist",
"node_modules",
"venv",
# protobuf generated files
"*_pb2.py",
"*_pb2.pyi"
]

10
.github/tox.ini vendored
View File

@@ -1,10 +0,0 @@
[pycodestyle]
; E402: module level import not at top of file
; W503: line break before binary operator
; E231 missing whitespace after ',' (emitted by black)
; E203 whitespace before ':' (emitted by black)
ignore = E402,W503,E203,E231
max-line-length = 160
statistics = True
count = True
exclude = .*

View File

@@ -6,37 +6,47 @@ on:
release:
types: [edited, published]
permissions:
contents: write
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
fail-fast: true
matrix:
# using Python 3.8 to support running across multiple operating systems including Windows 7
include:
- os: ubuntu-18.04
- os: ubuntu-20.04
# use old linux so that the shared library versioning is more portable
artifact_name: capa
asset_name: linux
python_version: 3.8
- os: ubuntu-20.04
artifact_name: capa
asset_name: linux-py311
python_version: 3.11
- os: windows-2019
artifact_name: capa.exe
asset_name: windows
python_version: 3.8
- os: macos-11
# use older macOS for assumed better portability
artifact_name: capa
asset_name: macos
python_version: 3.8
steps:
- name: Checkout capa
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
with:
submodules: true
# using Python 3.8 to support running across multiple operating systems including Windows 7
- name: Set up Python 3.8
- name: Set up Python ${{ matrix.python_version }}
uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 # v4.5.0
with:
python-version: 3.8
- if: matrix.os == 'ubuntu-18.04'
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
@@ -52,25 +62,29 @@ jobs:
run: dist/capa "tests/data/499c2a85f6e8142c3f48d4251c9c7cd6.raw32"
- name: Does it run (ELF)?
run: dist/capa "tests/data/7351f8a40c5450557b24622417fc478d.elf_"
- name: Does it run (CAPE)?
run: |
7z e "tests/data/dynamic/cape/v2.2/d46900384c78863420fb3e297d0a2f743cd2b6b3f7f82bf64059a168e07aceb7.json.gz"
dist/capa "d46900384c78863420fb3e297d0a2f743cd2b6b3f7f82bf64059a168e07aceb7.json"
- uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
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:
matrix:
include:
# OSs not already tested above
- os: ubuntu-18.04
- os: ubuntu-22.04
artifact_name: capa
asset_name: linux
- os: ubuntu-20.04
- os: ubuntu-22.04
artifact_name: capa
asset_name: linux
asset_name: linux-py311
- os: windows-2022
artifact_name: capa.exe
asset_name: windows
@@ -96,6 +110,8 @@ jobs:
include:
- asset_name: linux
artifact_name: capa
- asset_name: linux-py311
artifact_name: capa
- asset_name: windows
artifact_name: capa.exe
- asset_name: macos

View File

@@ -7,6 +7,8 @@ on:
pull_request_target:
types: [opened, edited, synchronize]
permissions: read-all
jobs:
check_changelog:
# no need to check for dependency updates via dependabot

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

@@ -1,29 +1,41 @@
# This workflows will upload a Python Package using Twine when a release is created
# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
# use PyPI trusted publishing, as described here:
# https://blog.trailofbits.com/2023/05/23/trusted-publishing-a-new-benchmark-for-packaging-security/
name: publish to pypi
on:
release:
types: [published]
permissions:
contents: write
jobs:
deploy:
runs-on: ubuntu-20.04
pypi-publish:
runs-on: ubuntu-latest
environment:
name: release
permissions:
id-token: write
steps:
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
- name: Set up Python
uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 # v4.5.0
with:
python-version: '3.7'
python-version: '3.8'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install setuptools wheel twine
- name: Build and publish
env:
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
pip install -e .[build]
- name: build package
run: |
python setup.py sdist bdist_wheel
twine upload --skip-existing dist/*
python -m build
- name: upload package artifacts
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
with:
path: dist/*
- name: publish package
uses: pypa/gh-action-pypi-publish@f5622bde02b04381239da3573277701ceca8f6a0 # release/v1
with:
skip-existing: true
verbose: true
print-hash: true

View File

@@ -4,6 +4,8 @@ on:
release:
types: [published]
permissions: read-all
jobs:
tag:
name: Tag capa rules

View File

@@ -6,6 +6,8 @@ on:
pull_request:
branches: [ master ]
permissions: read-all
# save workspaces to speed up testing
env:
CAPA_SAVE_WORKSPACE: "True"
@@ -27,20 +29,23 @@ jobs:
steps:
- name: Checkout capa
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
- name: Set up Python 3.8
# use latest available python to take advantage of best performance
- name: Set up Python 3.11
uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 # v4.5.0
with:
python-version: "3.8"
python-version: "3.11"
- name: Install dependencies
run: pip install -e .[dev]
- name: Lint with ruff
run: pre-commit run ruff
- name: Lint with isort
run: isort --profile black --length-sort --line-width 120 --skip-glob "*_pb2.py" -c .
run: pre-commit run isort --show-diff-on-failure
- name: Lint with black
run: black -l 120 --extend-exclude ".*_pb2.py" --check .
- name: Lint with pycodestyle
run: pycodestyle --exclude="*_pb2.py" --show-source capa/ scripts/ tests/
run: pre-commit run black --show-diff-on-failure
- name: Lint with flake8
run: pre-commit run flake8 --hook-stage manual
- name: Check types with mypy
run: mypy --config-file .github/mypy/mypy.ini --check-untyped-defs capa/ scripts/ tests/
run: pre-commit run mypy --hook-stage manual
rule_linter:
runs-on: ubuntu-20.04
@@ -49,12 +54,12 @@ jobs:
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
with:
submodules: recursive
- name: Set up Python 3.8
- name: Set up Python 3.11
uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 # v4.5.0
with:
python-version: "3.8"
python-version: "3.11"
- name: Install capa
run: pip install -e .
run: pip install -e .[dev]
- name: Run rule linter
run: python scripts/lint.py rules/
@@ -67,13 +72,15 @@ jobs:
matrix:
os: [ubuntu-20.04, windows-2019, macos-11]
# across all operating systems
python-version: ["3.7", "3.11"]
python-version: ["3.8", "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"
steps:
- name: Checkout capa with submodules
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
@@ -88,5 +95,110 @@ jobs:
run: sudo apt-get install -y libyaml-dev
- name: Install capa
run: pip install -e .[dev]
- 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/
binja-tests:
name: Binary Ninja tests for ${{ matrix.python-version }}
env:
BN_SERIAL: ${{ secrets.BN_SERIAL }}
runs-on: ubuntu-20.04
needs: [tests]
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "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
with:
submodules: recursive
- name: Set up Python ${{ matrix.python-version }}
if: ${{ env.BN_SERIAL != 0 }}
uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 # v4.5.0
with:
python-version: ${{ matrix.python-version }}
- name: Install pyyaml
if: ${{ env.BN_SERIAL != 0 }}
run: sudo apt-get install -y libyaml-dev
- name: Install capa
if: ${{ env.BN_SERIAL != 0 }}
run: pip install -e .[dev]
- name: install Binary Ninja
if: ${{ env.BN_SERIAL != 0 }}
run: |
mkdir ./.github/binja
curl "https://raw.githubusercontent.com/Vector35/binaryninja-api/6812c97/scripts/download_headless.py" -o ./.github/binja/download_headless.py
python ./.github/binja/download_headless.py --serial ${{ env.BN_SERIAL }} --output .github/binja/BinaryNinja-headless.zip
unzip .github/binja/BinaryNinja-headless.zip -d .github/binja/
python .github/binja/binaryninja/scripts/install_api.py --install-on-root --silent
- name: Run tests
if: ${{ env.BN_SERIAL != 0 }}
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.8", "3.11"]
java-version: ["17"]
gradle-version: ["7.3"]
ghidra-version: ["10.3"]
public-version: ["PUBLIC_20230510"] # for ghidra releases
jep-version: ["4.1.1"]
ghidrathon-version: ["3.0.0"]
steps:
- name: Checkout capa with submodules
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
with:
submodules: true
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 # v4.5.0
with:
python-version: ${{ matrix.python-version }}
- name: Set up Java ${{ matrix.java-version }}
uses: actions/setup-java@5ffc13f4174014e2d4d4572b3d74c3fa61aeb2c2 # v3
with:
distribution: 'temurin'
java-version: ${{ matrix.java-version }}
- name: Set up Gradle ${{ matrix.gradle-version }}
uses: gradle/gradle-build-action@40b6781dcdec2762ad36556682ac74e31030cfe2 # v2.5.1
with:
gradle-version: ${{ matrix.gradle-version }}
- name: Install Jep ${{ matrix.jep-version }}
run : pip install jep==${{ matrix.jep-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
curl -o ./.github/ghidrathon/ghidrathon-${{ matrix.ghidrathon-version }}.zip "https://codeload.github.com/mandiant/Ghidrathon/zip/refs/tags/v${{ matrix.ghidrathon-version }}"
unzip .github/ghidrathon/ghidrathon-${{ matrix.ghidrathon-version }}.zip -d .github/ghidrathon/
gradle -p ./.github/ghidrathon/Ghidrathon-${{ matrix.ghidrathon-version }}/ -PGHIDRA_INSTALL_DIR=$(pwd)/.github/ghidra/ghidra_${{ matrix.ghidra-version }}_PUBLIC
unzip .github/ghidrathon/Ghidrathon-${{ matrix.ghidrathon-version }}/dist/*.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 -e .[dev]
- 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

14
.gitignore vendored
View File

@@ -108,17 +108,21 @@ venv.bak/
*.viv
*.idb
*.i64
.vscode
!rules/lib
# hooks/ci.sh output
isort-output.log
black-output.log
rule-linter-output.log
.vscode
scripts/perf/*.txt
scripts/perf/*.svg
scripts/perf/*.zip
.direnv
.envrc
.DS_Store
*/.DS_Store
Pipfile
Pipfile.lock
/cache/
.github/binja/binaryninja
.github/binja/download_headless.py
.github/binja/BinaryNinja-headless.zip

2
.gitmodules vendored
View File

@@ -1,6 +1,8 @@
[submodule "rules"]
path = rules
url = ../capa-rules.git
branch = dynamic-syntax
[submodule "tests/data"]
path = tests/data
url = ../capa-testfiles.git
branch = dynamic-feature-extractor

129
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,129 @@
# install the pre-commit hooks:
#
# pre-commit install --hook-type pre-commit
# pre-commit installed at .git/hooks/pre-commit
#
# pre-commit install --hook-type pre-push
# pre-commit installed at .git/hooks/pre-push
#
# run all linters liks:
#
# pre-commit run --all-files
# isort....................................................................Passed
# black....................................................................Passed
# ruff.....................................................................Passed
# flake8...................................................................Passed
# mypy.....................................................................Passed
#
# run a single linter like:
#
# pre-commit run --all-files isort
# isort....................................................................Passed
repos:
- repo: local
hooks:
- id: isort
name: isort
stages: [commit, push, manual]
language: system
entry: isort
args:
- "--length-sort"
- "--profile"
- "black"
- "--line-length=120"
- "--skip-glob"
- "*_pb2.py"
- "capa/"
- "scripts/"
- "tests/"
always_run: true
pass_filenames: false
- repo: local
hooks:
- id: black
name: black
stages: [commit, push, manual]
language: system
entry: black
args:
- "--line-length=120"
- "--extend-exclude"
- ".*_pb2.py"
- "capa/"
- "scripts/"
- "tests/"
always_run: true
pass_filenames: false
- repo: local
hooks:
- id: ruff
name: ruff
stages: [commit, push, manual]
language: system
entry: ruff
args:
- "check"
- "--config"
- ".github/ruff.toml"
- "capa/"
- "scripts/"
- "tests/"
always_run: true
pass_filenames: false
- repo: local
hooks:
- id: flake8
name: flake8
stages: [push, manual]
language: system
entry: flake8
args:
- "--config"
- ".github/flake8.ini"
- "--extend-exclude"
- "capa/render/proto/capa_pb2.py"
- "capa/"
- "scripts/"
- "tests/"
always_run: true
pass_filenames: false
- repo: local
hooks:
- id: mypy
name: mypy
stages: [push, manual]
language: system
entry: mypy
args:
- "--check-untyped-defs"
- "--ignore-missing-imports"
- "--config-file=.github/mypy/mypy.ini"
- "capa/"
- "scripts/"
- "tests/"
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

View File

@@ -3,27 +3,263 @@
## master (unreleased)
### New Features
- add protobuf format for result documents #1219 @williballenthin
- add Ghidra backend #1770 #1767 @colton-gabertan @mike-hunhoff
- add dynamic analysis via CAPE sandbox reports #48 #1535 @yelhamer
- add call scope #771 @yelhamer
- add thread scope #1517 @yelhamer
- add process scope #1517 @yelhamer
- rules: change `meta.scope` to `meta.scopes` @yelhamer
- protobuf: add `Metadata.flavor` @williballenthin
- binja: add support for forwarded exports #1646 @xusheng6
- binja: add support for symtab names #1504 @xusheng6
- add com class/interface features #322 @Aayush-goel-04
- dotnet: emit enclosing class information for nested classes #1780 #1913 @bkojusner @mike-hunhoff
### Breaking Changes
### New Rules (3)
- remove the `SCOPE_*` constants in favor of the `Scope` enum #1764 @williballenthin
- protobuf: deprecate `RuleMetadata.scope` in favor of `RuleMetadata.scopes` @williballenthin
- protobuf: deprecate `Metadata.analysis` in favor of `Metadata.analysis2` that is dynamic analysis aware @williballenthin
- update freeze format to v3, adding support for dynamic analysis @williballenthin
- extractor: ignore DLL name for api features #1815 @mr-tz
- persistence/scheduled-tasks/schedule-task-via-at joren485
- data-manipulation/prng/generate-random-numbers-via-rtlgenrandom william.ballenthin@mandiant.com
- communication/ip/convert-ip-address-from-string @mr-tz
### New Rules (39)
- nursery/get-ntoskrnl-base-address @mr-tz
- host-interaction/network/connectivity/set-tcp-connection-state @johnk3r
- nursery/capture-process-snapshot-data @mr-tz
- collection/network/capture-packets-using-sharppcap jakub.jozwiak@mandiant.com
- nursery/communicate-with-kernel-module-via-netlink-socket-on-linux michael.hunhoff@mandiant.com
- nursery/get-current-pid-on-linux michael.hunhoff@mandiant.com
- nursery/get-file-system-information-on-linux michael.hunhoff@mandiant.com
- nursery/get-password-database-entry-on-linux michael.hunhoff@mandiant.com
- nursery/mark-thread-detached-on-linux michael.hunhoff@mandiant.com
- nursery/persist-via-gnome-autostart-on-linux michael.hunhoff@mandiant.com
- nursery/set-thread-name-on-linux michael.hunhoff@mandiant.com
- load-code/dotnet/load-windows-common-language-runtime michael.hunhoff@mandiant.com blas.kojusner@mandiant.com jakub.jozwiak@mandiant.com
- nursery/log-keystrokes-via-input-method-manager @mr-tz
- nursery/encrypt-data-using-rc4-via-systemfunction032 richard.weiss@mandiant.com
- nursery/add-value-to-global-atom-table @mr-tz
- nursery/enumerate-processes-that-use-resource @Ana06
- host-interaction/process/inject/allocate-or-change-rwx-memory @mr-tz
- lib/allocate-or-change-rw-memory 0x534a@mailbox.org @mr-tz
- lib/change-memory-protection @mr-tz
- anti-analysis/anti-av/patch-antimalware-scan-interface-function jakub.jozwiak@mandiant.com
- executable/dotnet-singlefile/bundled-with-dotnet-single-file-deployment sara.rincon@mandiant.com
- internal/limitation/file/internal-dotnet-single-file-deployment-limitation sara.rincon@mandiant.com
- data-manipulation/encoding/encode-data-using-add-xor-sub-operations jakub.jozwiak@mandiant.com
- nursery/access-camera-in-dotnet-on-android michael.hunhoff@mandiant.com
- nursery/capture-microphone-audio-in-dotnet-on-android michael.hunhoff@mandiant.com
- nursery/capture-screenshot-in-dotnet-on-android michael.hunhoff@mandiant.com
- nursery/check-for-incoming-call-in-dotnet-on-android michael.hunhoff@mandiant.com
- nursery/check-for-outgoing-call-in-dotnet-on-android michael.hunhoff@mandiant.com
- nursery/compiled-with-xamarin michael.hunhoff@mandiant.com
- nursery/get-os-version-in-dotnet-on-android michael.hunhoff@mandiant.com
- data-manipulation/compression/create-cabinet-on-windows michael.hunhoff@mandiant.com jakub.jozwiak@mandiant.com
- data-manipulation/compression/extract-cabinet-on-windows jakub.jozwiak@mandiant.com
- lib/create-file-decompression-interface-context-on-windows jakub.jozwiak@mandiant.com
- nursery/enumerate-files-in-dotnet moritz.raabe@mandiant.com anushka.virgaonkar@mandiant.com
- nursery/get-mac-address-in-dotnet moritz.raabe@mandiant.com michael.hunhoff@mandiant.com echernofsky@google.com
- nursery/get-current-process-command-line william.ballenthin@mandiant.com
- nursery/get-current-process-file-path william.ballenthin@mandiant.com
- nursery/hook-routines-via-dlsym-rtld_next william.ballenthin@mandiant.com
-
### Bug Fixes
- ghidra: fix `ints_to_bytes` performance #1761 @mike-hunhoff
- binja: improve function call site detection @xusheng6
- binja: use `binaryninja.load` to open files @xusheng6
- binja: bump binja version to 3.5 #1789 @xusheng6
- elf: better detect ELF OS via GCC .ident directives #1928 @williballenthin
### capa explorer IDA Pro plugin
### Development
- update ATT&CK/MBC data for linting #1932 @mr-tz
### Raw diffs
- [capa v5.0.0...master](https://github.com/mandiant/capa/compare/v5.0.0...master)
- [capa-rules v5.0.0...master](https://github.com/mandiant/capa-rules/compare/v5.0.0...master)
- [capa v6.1.0...master](https://github.com/mandiant/capa/compare/v6.1.0...master)
- [capa-rules v6.1.0...master](https://github.com/mandiant/capa-rules/compare/v6.1.0...master)
## v6.1.0
capa v6.1.0 is a bug fix release, most notably fixing unhandled exceptions in the capa explorer IDA Pro plugin.
@Aayush-Goel-04 put a lot of effort into improving code quality and adding a script for rule authors.
The script shows which features are present in a sample but not referenced by any existing rule.
You could use this script to find opportunities for new rules.
Speaking of new rules, we have eight additions, coming from Ronnie, Jakub, Moritz, Ervin, and still@teamt5.org!
### New Features
- ELF: implement import and export name extractor #1607 #1608 @Aayush-Goel-04
- bump pydantic from 1.10.9 to 2.1.1 #1582 @Aayush-Goel-04
- develop script to highlight features not used during matching #331 @Aayush-Goel-04
### New Rules (8)
- executable/pe/export/forwarded-export ronnie.salomonsen@mandiant.com
- host-interaction/bootloader/get-uefi-variable jakub.jozwiak@mandiant.com
- host-interaction/bootloader/set-uefi-variable jakub.jozwiak@mandiant.com
- nursery/enumerate-device-drivers-on-linux @mr-tz
- anti-analysis/anti-vm/vm-detection/check-for-foreground-window-switch ervin.ocampo@mandiant.com
- linking/static/sqlite3/linked-against-cppsqlite3 still@teamt5.org
- linking/static/sqlite3/linked-against-sqlite3 still@teamt5.org
### Bug Fixes
- rules: fix forwarded export characteristic #1656 @RonnieSalomonsen
- Binary Ninja: Fix stack string detection #1473 @xusheng6
- linter: skip native API check for NtProtectVirtualMemory #1675 @williballenthin
- OS: detect Android ELF files #1705 @williballenthin
- ELF: fix parsing of symtab #1704 @williballenthin
- result document: don't use deprecated pydantic functions #1718 @williballenthin
- pytest: don't mark IDA tests as pytest tests #1719 @williballenthin
### capa explorer IDA Pro plugin
- fix unhandled exception when resolving rule path #1693 @mike-hunhoff
### Raw diffs
- [capa v6.0.0...v6.1.0](https://github.com/mandiant/capa/compare/v6.0.0...v6.1.0)
- [capa-rules v6.0.0...v6.1.0](https://github.com/mandiant/capa-rules/compare/v6.0.0...v6.1.0)
## v6.0.0
capa v6.0 brings many bug fixes and quality improvements, including 64 rule updates and 26 new rules. We're now publishing to PyPI via [Trusted Publishing](https://blog.pypi.org/posts/2023-04-20-introducing-trusted-publishers/) and have migrated to using a `pyproject.toml` file. @Aayush-Goel-04 contributed a lot of new code across many files, so please welcome them to the project, along with @anders-v @crowface28 @dkelly2e @RonnieSalomonsen and @ejfocampo as first-time rule contributors!
For those that use capa as a library, we've introduced some limited breaking changes that better represent data types (versus less-structured data like dictionaries and strings). With the recent deprecation, we've also dropped support for Python 3.7.
### New Features
- add script to detect feature overlap between new and existing capa rules [#1451](https://github.com/mandiant/capa/issues/1451) [@Aayush-Goel-04](https://github.com/aayush-goel-04)
- extract forwarded exports from PE files #1624 @williballenthin
- extract function and API names from ELF symtab entries @yelhamer https://github.com/mandiant/capa-rules/issues/736
- use fancy box drawing characters for default output #1586 @williballenthin
### Breaking Changes
- use a class to represent Metadata (not dict) #1411 @Aayush-Goel-04 @manasghandat
- use pathlib.Path to represent file paths #1534 @Aayush-Goel-04
- Python 3.8 is now the minimum supported Python version #1578 @williballenthin
- Require a Contributor License Agreement (CLA) for PRs going forward #1642 @williballenthin
### New Rules (26)
- load-code/shellcode/execute-shellcode-via-windows-callback-function ervin.ocampo@mandiant.com jakub.jozwiak@mandiant.com
- nursery/execute-shellcode-via-indirect-call ronnie.salomonsen@mandiant.com
- data-manipulation/encryption/aes/encrypt-data-using-aes-mixcolumns-step @mr-tz
- linking/static/aplib/linked-against-aplib still@teamt5.org
- communication/mailslot/read-from-mailslot nick.simonian@mandiant.com
- nursery/hash-data-using-sha512managed-in-dotnet jonathanlepore@google.com
- nursery/compiled-with-exescript jonathanlepore@google.com
- nursery/check-for-sandbox-via-mac-address-ouis-in-dotnet jonathanlepore@google.com
- host-interaction/hardware/enumerate-devices-by-category @mr-tz
- host-interaction/service/continue-service @mr-tz
- host-interaction/service/pause-service @mr-tz
- persistence/exchange/act-as-exchange-transport-agent jakub.jozwiak@mandiant.com
- host-interaction/file-system/create-virtual-file-system-in-dotnet jakub.jozwiak@mandiant.com
- compiler/cx_freeze/compiled-with-cx_freeze @mr-tz jakub.jozwiak@mandiant.com
- communication/socket/create-vmci-socket jakub.jozwiak@mandiant.com
- persistence/office/act-as-excel-xll-add-in jakub.jozwiak@mandiant.com
- persistence/office/act-as-office-com-add-in jakub.jozwiak@mandiant.com
- persistence/office/act-as-word-wll-add-in jakub.jozwiak@mandiant.com
- anti-analysis/anti-debugging/debugger-evasion/hide-thread-from-debugger michael.hunhoff@mandiant.com jakub.jozwiak@mandiant.com
- host-interaction/memory/create-new-application-domain-in-dotnet jakub.jozwiak@mandiant.com
- host-interaction/gui/switch-active-desktop jakub.jozwiak@mandiant.com
- host-interaction/service/query-service-configuration @mr-tz
- anti-analysis/anti-av/patch-event-tracing-for-windows-function jakub.jozwiak@mandiant.com
- data-manipulation/encoding/xor/covertly-decode-and-write-data-to-windows-directory-using-indirect-calls dan.kelly@mandiant.com
- linking/runtime-linking/resolve-function-by-brute-ratel-badger-hash jakub.jozwiak@mandiant.com
### Bug Fixes
- extractor: add a Binary Ninja test that asserts its version #1487 @xusheng6
- extractor: update Binary Ninja stack string detection after the new constant outlining feature #1473 @xusheng6
- extractor: update vivisect Arch extraction #1334 @mr-tz
- extractor: avoid Binary Ninja exception when analyzing certain files #1441 @xusheng6
- symtab: fix struct.unpack() format for 64-bit ELF files @yelhamer
- symtab: safeguard against ZeroDivisionError for files containing a symtab with a null entry size @yelhamer
- improve ELF strtab and needed parsing @mr-tz
- better handle exceptional cases when parsing ELF files #1458 @Aayush-Goel-04
- improved testing coverage for Binary Ninja backend #1446 @Aayush-Goel-04
- add logging and print redirect to tqdm for capa main #749 @Aayush-Goel-04
- extractor: fix binja installation path detection does not work with Python 3.11
- tests: refine the IDA test runner script #1513 @williballenthin
- output: don't leave behind traces of progress bar @williballenthin
- import-to-ida: fix bug introduced with JSON report changes in v5 #1584 @williballenthin
- main: don't show spinner when emitting debug messages #1636 @williballenthin
- rules: add forwarded export characteristics to rule syntax file scope #1653 @RonnieSalomonsen
### capa explorer IDA Pro plugin
### Development
- update ATT&CK/MBC data for linting #1568 @mr-tz
- log time taken to analyze each function #1290 @williballenthin
- tests: make fixture available via conftest.py #1592 @williballenthin
- publish via PyPI trusted publishing #1491 @williballenthin
- migrate to pyproject.toml #1301 @williballenthin
- use [pre-commit](https://pre-commit.com/) to invoke linters #1579 @williballenthin
### Raw diffs
- [capa v5.1.0...v6.0.0](https://github.com/mandiant/capa/compare/v5.1.0...v6.0.0)
- [capa-rules v5.1.0...v6.0.0](https://github.com/mandiant/capa-rules/compare/v5.1.0...v6.0.0)
## v5.1.0
capa version 5.1.0 adds a Protocol Buffers (protobuf) format for result documents. Additionally, the [Vector35](https://vector35.com/) team contributed a new feature extractor using Binary Ninja. Other new features are a new CLI flag to override the detected operating system, functionality to read and render existing result documents, and a output color format that's easier to read.
Over 25 capa rules have been added and improved.
Thanks for all the support, especially to @xusheng6, @captainGeech42, @ggold7046, @manasghandat, @ooprathamm, @linpeiyu164, @yelhamer, @HongThatCong, @naikordian, @stevemk14ebr, @emtuls, @raymondlleong, @bkojusner, @joren485, and everyone else who submitted bugs and provided feedback!
### New Features
- add protobuf format for result documents #1219 @williballenthin @mr-tz
- extractor: add Binary Ninja feature extractor @xusheng6
- new cli flag `--os` to override auto-detected operating system for a sample @captainGeech42
- change colour/highlight to "cyan" instead of "blue" for better readability #1384 @ggold7046
- add new format to parse output json back to capa #1396 @ooprathamm
- parse ELF symbols' names to guess OS #1403 @yelhamer
### New Rules (26)
- persistence/scheduled-tasks/schedule-task-via-at joren485
- data-manipulation/prng/generate-random-numbers-via-rtlgenrandom william.ballenthin@mandiant.com
- communication/ip/convert-ip-address-from-string @mr-tz
- data-manipulation/compression/compress-data-via-zlib-inflate-or-deflate blas.kojusner@mandiant.com
- executable/installer/dotnet/packaged-as-single-file-dotnet-application michael.hunhoff@mandiant.com
- communication/socket/create-raw-socket blas.kojusner@mandiant.com
- communication/http/reference-http-user-agent-string @mr-tz
- communication/http/get-http-content-length william.ballenthin@mandiant.com
- nursery/move-directory michael.hunhoff@mandiant.com
- nursery/get-http-request-uri william.ballenthin@mandiant.com
- nursery/create-zip-archive-in-dotnet michael.hunhoff@mandiant.com
- nursery/extract-zip-archive-in-dotnet anushka.virgaonkar@mandiant.com michael.hunhoff@mandiant.com
- data-manipulation/encryption/tea/decrypt-data-using-tea william.ballenthin@mandiant.com raymond.leong@mandiant.com
- data-manipulation/encryption/tea/encrypt-data-using-tea william.ballenthin@mandiant.com raymond.leong@mandiant.com
- data-manipulation/encryption/xtea/encrypt-data-using-xtea raymond.leong@mandiant.com
- data-manipulation/encryption/xxtea/encrypt-data-using-xxtea raymond.leong@mandiant.com
- nursery/hash-data-using-ripemd128 raymond.leong@mandiant.com
- nursery/hash-data-using-ripemd256 raymond.leong@mandiant.com
- nursery/hash-data-using-ripemd320 raymond.leong@mandiant.com
- nursery/set-web-proxy-in-dotnet michael.hunhoff@mandiant.com
- nursery/check-for-windows-sandbox-via-subdirectory echernofsky@google.com
- nursery/enumerate-pe-sections-in-dotnet @mr-tz
- nursery/destroy-software-breakpoint-capability echernofsky@google.com
- nursery/send-data-to-internet michael.hunhoff@mandiant.com
- nursery/compiled-with-cx_freeze @mr-tz
- nursery/contain-a-thread-local-storage-tls-section-in-dotnet michael.hunhoff@mandiant.com
### Bug Fixes
- extractor: interface of cache modified to prevent extracting file and global features multiple times @stevemk14ebr
- extractor: removed '.dynsym' as the library name for ELF imports #1318 @stevemk14ebr
- extractor: fix vivisect loop detection corner case #1310 @mr-tz
- match: extend OS characteristic to match OS_ANY to all supported OSes #1324 @mike-hunhoff
- extractor: fix IDA and vivisect string and bytes features overlap and tests #1327 #1336 @xusheng6
### capa explorer IDA Pro plugin
- rule generator plugin now loads faster when jumping between functions @stevemk14ebr
- fix exception when plugin loaded in IDA hosted under idat #1341 @mike-hunhoff
- improve embedded PE detection performance and reduce FP potential #1344 @mike-hunhoff
### Raw diffs
- [capa v5.0.0...v5.1.0](https://github.com/mandiant/capa/compare/v5.0.0...v5.1.0)
- [capa-rules v5.0.0...v5.1.0](https://github.com/mandiant/capa-rules/compare/v5.0.0...v5.1.0)
## v5.0.0 (2023-02-08)

View File

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

131
README.md
View File

@@ -2,13 +2,13 @@
[![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-773-blue.svg)](https://github.com/mandiant/capa-rules)
[![Number of rules](https://img.shields.io/badge/rules-864-blue.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)
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:
@@ -125,6 +125,96 @@ function @ 0x4011C0
...
```
Additionally, capa also supports analyzing [CAPE](https://github.com/kevoreilly/CAPEv2) sandbox reports for dynamic capabilty extraction.
In order to use this, you first submit your sample to CAPE for analysis, and then run capa against the generated report (JSON).
Here's an example of running capa against a packed binary, and then running capa against the CAPE report of that binary:
```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 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,31 +225,30 @@ 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.
@@ -170,6 +259,8 @@ capa explorer helps you identify interesting areas of a program and build new ca
![capa + IDA Pro integration](https://github.com/mandiant/capa/blob/master/doc/img/explorer_expanded.png)
If you use Ghidra, you can use the Python 3 [Ghidra feature extractor](/capa/ghidra/). This integration enables capa to extract features directly from your Ghidra database, which can help you identify capabilities in programs that you analyze using Ghidra.
# 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, Tuple
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,198 @@
# -*- 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, Tuple
import tqdm
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.helpers import redirecting_print_to_tqdm
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, thats 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, thats ok.
thread_matches: MatchResults = collections.defaultdict(list)
# matches found at the call scope.
# might be found at different calls, thats 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)
with redirecting_print_to_tqdm(disable_progress):
with tqdm.contrib.logging.logging_redirect_tqdm():
pbar = tqdm.tqdm
if disable_progress:
# do not use tqdm to avoid unnecessary side effects when caller intends
# to disable progress completely
def pbar(s, *args, **kwargs):
return s
processes = list(extractor.get_processes())
pb = pbar(processes, desc="matching", unit=" processes", leave=False)
for p in pb:
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)
# 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

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

@@ -0,0 +1,233 @@
# -*- 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, Tuple
import tqdm.contrib.logging
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.helpers import redirecting_print_to_tqdm
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, thats 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, thats ok.
bb_matches: MatchResults = collections.defaultdict(list)
# matches found at the instruction scope.
# might be found at different instructions, thats 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)
with redirecting_print_to_tqdm(disable_progress):
with tqdm.contrib.logging.logging_redirect_tqdm():
pbar = tqdm.tqdm
if capa.helpers.is_runtime_ghidra():
# Ghidrathon interpreter cannot properly handle
# the TMonitor thread that is created via a monitor_interval
# > 0
pbar.monitor_interval = 0
if disable_progress:
# do not use tqdm to avoid unnecessary side effects when caller intends
# to disable progress completely
def pbar(s, *args, **kwargs):
return s
functions = list(extractor.get_functions())
n_funcs = len(functions)
pb = pbar(functions, desc="matching", unit=" functions", postfix="skipped 0 library functions", leave=False)
for f in pb:
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))
if isinstance(pb, tqdm.tqdm):
pb.set_postfix_str(f"skipped {n_libs} library functions ({percentage}%)")
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 = sum(len(res) for res in function_matches.values())
match_count += sum(len(res) for res in bb_matches.values())
match_count += sum(len(res) for res in insn_matches.values())
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)
# 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 = 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) 2020 Mandiant, Inc. All Rights Reserved.
# 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
@@ -8,7 +8,7 @@
import copy
import collections
from typing import TYPE_CHECKING, Set, Dict, List, Tuple, Union, Mapping, Iterable, Iterator, cast
from typing import TYPE_CHECKING, Set, Dict, List, Tuple, Union, Mapping, Iterable, Iterator
import capa.perf
import capa.features.common
@@ -43,10 +43,12 @@ class Statement:
self.description = description
def __str__(self):
name = self.name.lower()
children = ",".join(map(str, self.get_children()))
if self.description:
return "%s(%s = %s)" % (self.name.lower(), ",".join(map(str, self.get_children())), self.description)
return f"{name}({children} = {self.description})"
else:
return "%s(%s)" % (self.name.lower(), ",".join(map(str, self.get_children())))
return f"{name}({children})"
def __repr__(self):
return str(self)
@@ -69,7 +71,7 @@ class Statement:
yield child
if hasattr(self, "children"):
for child in getattr(self, "children"):
for child in self.children:
assert isinstance(child, (Statement, Feature))
yield child
@@ -81,7 +83,7 @@ class Statement:
self.child = new
if hasattr(self, "children"):
children = getattr(self, "children")
children = self.children
for i, child in enumerate(children):
if child is existing:
children[i] = new
@@ -232,9 +234,9 @@ class Range(Statement):
def __str__(self):
if self.max == (1 << 64 - 1):
return "range(%s, min=%d, max=infinity)" % (str(self.child), self.min)
return f"range({str(self.child)}, min={self.min}, max=infinity)"
else:
return "range(%s, min=%d, max=%d)" % (str(self.child), self.min, self.max)
return f"range({str(self.child)}, min={self.min}, max={self.max})"
class Subscope(Statement):
@@ -302,7 +304,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,3 +1,10 @@
# 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.
class UnsupportedRuntimeError(RuntimeError):
pass
@@ -12,3 +19,7 @@ class UnsupportedArchError(ValueError):
class UnsupportedOSError(ValueError):
pass
class EmptyReportError(ValueError):
pass

View File

@@ -1,3 +1,10 @@
# 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 abc
@@ -36,6 +43,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):
"""addesses 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) 2020 Mandiant, Inc. All Rights Reserved.
# 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

View File

@@ -0,0 +1,36 @@
# 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 enum import Enum
from typing import Dict, List
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)

3696
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) 2020 Mandiant, Inc. All Rights Reserved.
# 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
@@ -100,7 +100,10 @@ class Result:
return self.success
class Feature(abc.ABC):
class Feature(abc.ABC): # noqa: B024
# this is an abstract class, since we don't want anyone to instantiate it directly,
# but it doesn't have any abstract methods.
def __init__(
self,
value: Union[str, int, float, bytes],
@@ -124,12 +127,17 @@ class Feature(abc.ABC):
return self.name == other.name and self.value == other.value
def __lt__(self, other):
# TODO: this is a huge hack!
# implementing sorting by serializing to JSON is a huge hack.
# its 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
# we should fix if this wasn't already a huge hack.
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:
@@ -149,11 +157,11 @@ class Feature(abc.ABC):
def __str__(self):
if self.value is not None:
if self.description:
return "%s(%s = %s)" % (self.get_name_str(), self.get_value_str(), self.description)
return f"{self.get_name_str()}({self.get_value_str()} = {self.description})"
else:
return "%s(%s)" % (self.get_name_str(), self.get_value_str())
return f"{self.get_name_str()}({self.get_value_str()})"
else:
return "%s" % self.get_name_str()
return f"{self.get_name_str()}"
def __repr__(self):
return str(self)
@@ -242,7 +250,7 @@ class Substring(String):
def __str__(self):
assert isinstance(self.value, str)
return "substring(%s)" % escape_string(self.value)
return f"substring({escape_string(self.value)})"
class _MatchedSubstring(Substring):
@@ -267,11 +275,9 @@ class _MatchedSubstring(Substring):
self.matches = matches
def __str__(self):
matches = ", ".join(f'"{s}"' for s in (self.matches or {}).keys())
assert isinstance(self.value, str)
return 'substring("%s", matches = %s)' % (
self.value,
", ".join(map(lambda s: '"' + s + '"', (self.matches or {}).keys())),
)
return f'substring("{self.value}", matches = {matches})'
class Regex(String):
@@ -290,7 +296,7 @@ class Regex(String):
if value.endswith("/i"):
value = value[: -len("i")]
raise ValueError(
"invalid regular expression: %s it should use Python syntax, try it at https://pythex.org" % value
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):
@@ -336,7 +342,7 @@ class Regex(String):
def __str__(self):
assert isinstance(self.value, str)
return "regex(string =~ %s)" % self.value
return f"regex(string =~ {self.value})"
class _MatchedRegex(Regex):
@@ -361,11 +367,9 @@ class _MatchedRegex(Regex):
self.matches = matches
def __str__(self):
matches = ", ".join(f'"{s}"' for s in (self.matches or {}).keys())
assert isinstance(self.value, str)
return "regex(string =~ %s, matches = %s)" % (
self.value,
", ".join(map(lambda s: '"' + s + '"', (self.matches or {}).keys())),
)
return f"regex(string =~ {self.value}, matches = {matches})"
class StringFactory:
@@ -421,6 +425,8 @@ OS_MACOS = "macos"
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})
# internal only, not to be used in rules
OS_AUTO = "auto"
class OS(Feature):
@@ -428,6 +434,20 @@ class OS(Feature):
super().__init__(value, description=description)
self.name = "os"
def evaluate(self, ctx, **kwargs):
capa.perf.counters["evaluate.feature"] += 1
capa.perf.counters["evaluate.feature." + self.name] += 1
for feature, locations in ctx.items():
if not isinstance(feature, (OS,)):
continue
assert isinstance(feature.value, str)
if OS_ANY in (self.value, feature.value) or self.value == feature.value:
return Result(True, self, [], locations=locations)
return Result(False, self, [])
FORMAT_PE = "pe"
FORMAT_ELF = "elf"
@@ -437,7 +457,19 @@ VALID_FORMAT = (FORMAT_PE, FORMAT_ELF, FORMAT_DOTNET)
FORMAT_AUTO = "auto"
FORMAT_SC32 = "sc32"
FORMAT_SC64 = "sc64"
FORMAT_CAPE = "cape"
STATIC_FORMATS = {
FORMAT_SC32,
FORMAT_SC64,
FORMAT_PE,
FORMAT_ELF,
FORMAT_DOTNET,
}
DYNAMIC_FORMATS = {
FORMAT_CAPE,
}
FORMAT_FREEZE = "freeze"
FORMAT_RESULT = "result"
FORMAT_UNKNOWN = "unknown"

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# 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
@@ -7,13 +7,18 @@
# 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 dataclasses import dataclass
# TODO(williballenthin): use typing.TypeAlias directly when Python 3.9 is deprecated
# https://github.com/mandiant/capa/issues/1699
from typing_extensions import TypeAlias
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 +27,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.
@@ -63,16 +86,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 +106,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,6 +126,12 @@ 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]]:
"""
@@ -262,3 +294,177 @@ class FeatureExtractor:
Tuple[Feature, Address]: feature and its location
"""
raise NotImplementedError()
@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()
FeatureExtractor: TypeAlias = Union[StaticFeatureExtractor, DynamicFeatureExtractor]

View File

@@ -0,0 +1,184 @@
# 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 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 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 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]]:
"""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:
yield Characteristic("tight loop"), bbh.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,
extract_bb_stackstring,
)

View File

@@ -0,0 +1,81 @@
# 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 List, Tuple, Iterator
import binaryninja as binja
import capa.features.extractors.elf
import capa.features.extractors.binja.file
import capa.features.extractors.binja.insn
import capa.features.extractors.binja.global_
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,
SampleHashes,
FunctionHandle,
StaticFeatureExtractor,
)
class BinjaFeatureExtractor(StaticFeatureExtractor):
def __init__(self, bv: binja.BinaryView):
super().__init__(hashes=SampleHashes.from_bytes(bv.file.raw.read(0, len(bv.file.raw))))
self.bv = bv
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))
def get_base_address(self):
return AbsoluteVirtualAddress(self.bv.start)
def extract_global_features(self):
yield from self.global_features
def extract_file_features(self):
yield from capa.features.extractors.binja.file.extract_features(self.bv)
def get_functions(self) -> Iterator[FunctionHandle]:
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]]:
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, mlil_bb))
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
for text, length in bb[0]:
insn = binja_helpers.DisassemblyInstruction(addr, length, text)
yield InsnHandle(address=AbsoluteVirtualAddress(addr), inner=insn)
addr += length
def extract_insn_features(self, fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle):
yield from capa.features.extractors.binja.insn.extract_features(fh, bbh, ih)

View File

@@ -0,0 +1,187 @@
# 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 struct
from typing import Tuple, Iterator
from binaryninja import Segment, BinaryView, SymbolType, SymbolBinding
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.address import NO_ADDRESS, Address, FileOffsetAddress, AbsoluteVirtualAddress
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:
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))
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
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)
def extract_file_export_names(bv: BinaryView) -> Iterator[Tuple[Feature, Address]]:
"""extract function exports"""
for sym in bv.get_symbols_of_type(SymbolType.FunctionSymbol):
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)
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]]:
"""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 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, 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, include_dll=True):
yield Import(name), addr
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]]:
"""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]]:
"""
extract the names of statically-linked library functions.
"""
for sym_name in bv.symbols:
for sym in bv.symbols[sym_name]:
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]]:
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 == "Raw":
# no file type to return when processing a binary file, but we want to continue processing
return
else:
raise NotImplementedError(f"unexpected file format: {view_type}")
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):
yield feature, addr
FILE_HANDLERS = (
extract_file_export_names,
extract_file_import_names,
extract_file_strings,
extract_file_section_names,
extract_file_embedded_pe,
extract_file_function_names,
extract_file_format,
)

View File

@@ -0,0 +1,35 @@
# 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 subprocess
from pathlib import Path
# 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
# 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"""
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())
"""
def find_binja_path() -> Path:
raw_output = subprocess.check_output(["python", "-c", code]).decode("ascii").strip()
return Path(bytes.fromhex(raw_output).decode("utf8"))
if __name__ == "__main__":
print(find_binja_path())

View File

@@ -0,0 +1,104 @@
# 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 Tuple, Iterator
from binaryninja import Function, BinaryView, SymbolType, RegisterValueType, LowLevelILOperation
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.base_extractor import FunctionHandle
def extract_function_calls_to(fh: FunctionHandle):
"""extract callers to a function"""
func: Function = fh.inner
for caller in func.caller_sites:
# 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
llil = caller.llil
if (llil is None) or llil.operation not in [
LowLevelILOperation.LLIL_CALL,
LowLevelILOperation.LLIL_CALL_STACK_ADJUST,
LowLevelILOperation.LLIL_JUMP,
LowLevelILOperation.LLIL_TAILCALL,
]:
continue
if llil.dest.value.type not in [
RegisterValueType.ImportedAddressValue,
RegisterValueType.ConstantValue,
RegisterValueType.ConstantPointerValue,
]:
continue
address = llil.dest.value.value
if address != func.start:
continue
yield Characteristic("calls to"), AbsoluteVirtualAddress(caller.address)
def extract_function_loop(fh: FunctionHandle):
"""extract loop indicators from a function"""
func: Function = fh.inner
edges = []
# construct control flow graph
for bb in func.basic_blocks:
for edge in bb.outgoing_edges:
edges.append((bb.start, edge.target.start))
if loops.has_loop(edges):
yield Characteristic("loop"), fh.address
def extract_recursive_call(fh: FunctionHandle):
"""extract recursive function call"""
func: Function = fh.inner
bv: BinaryView = func.view
if bv is None:
return
for ref in bv.get_code_refs(func.start):
if ref.function == func:
yield Characteristic("recursive call"), fh.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 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, extract_function_name)

View File

@@ -0,0 +1,60 @@
# 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 binaryninja import BinaryView
from capa.features.common import OS, OS_MACOS, ARCH_I386, ARCH_AMD64, OS_WINDOWS, Arch, Feature
from capa.features.address import NO_ADDRESS, Address
logger = logging.getLogger(__name__)
def extract_os(bv: BinaryView) -> Iterator[Tuple[Feature, Address]]:
name = bv.platform.name
if "-" in name:
name = name.split("-")[0]
if name == "windows":
yield OS(OS_WINDOWS), NO_ADDRESS
elif name == "macos":
yield OS(OS_MACOS), NO_ADDRESS
elif name in ["linux", "freebsd", "decree"]:
yield OS(name), 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", name)
return
def extract_arch(bv: BinaryView) -> Iterator[Tuple[Feature, Address]]:
arch = bv.arch.name
if arch == "x86_64":
yield Arch(ARCH_AMD64), NO_ADDRESS
elif arch == "x86":
yield Arch(ARCH_I386), NO_ADDRESS
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", arch)
return

View File

@@ -0,0 +1,69 @@
# 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
from typing import List, Callable
from dataclasses import dataclass
from binaryninja import BinaryView, LowLevelILInstruction
from binaryninja.architecture import InstructionTextToken
@dataclass
class DisassemblyInstruction:
address: int
length: int
text: List[InstructionTextToken]
LLIL_VISITOR = Callable[[LowLevelILInstruction, LowLevelILInstruction, int], bool]
def visit_llil_exprs(il: LowLevelILInstruction, func: LLIL_VISITOR):
# BN does not really support operand index at the disassembly level, so use the LLIL operand index as a substitute.
# Note, this is NOT always guaranteed to be the same as disassembly operand.
for i, op in enumerate(il.operands):
if isinstance(op, LowLevelILInstruction) and func(op, il, i):
visit_llil_exprs(op, func)
def unmangle_c_name(name: str) -> str:
# https://learn.microsoft.com/en-us/cpp/build/reference/decorated-names?view=msvc-170#FormatC
# Possible variations for BaseThreadInitThunk:
# @BaseThreadInitThunk@12
# _BaseThreadInitThunk
# _BaseThreadInitThunk@12
# It is also possible for a function to have a `Stub` appended to its name:
# _lstrlenWStub@4
# A small optimization to avoid running the regex too many times
# this still increases the unit test execution time from 170s to 200s, should be able to accelerate it
#
# TODO(xusheng): performance optimizations to improve test execution time
# https://github.com/mandiant/capa/issues/1610
if name[0] in ["@", "_"]:
match = re.match(r"^[@|_](.*?)(Stub)?(@\d+)?$", name)
if match:
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)

View File

@@ -0,0 +1,586 @@
# 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, List, Tuple, Iterator, Optional
from binaryninja import Function
from binaryninja import BasicBlock as BinjaBasicBlock
from binaryninja import (
BinaryView,
ILRegister,
SymbolType,
BinaryReader,
RegisterValueType,
LowLevelILOperation,
LowLevelILInstruction,
)
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.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
# check if a function is a stub function to another function/symbol. The criteria is:
# 1. The function must only have one basic block
# 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
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
if call_count == 1 and call_target is not None:
return call_target
return None
def extract_insn_api_features(fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle) -> Iterator[Tuple[Feature, Address]]:
"""
parse instruction API features
example:
call dword [0x00473038]
"""
func: Function = fh.inner
bv: BinaryView = func.view
for llil in func.get_llils_at(ih.address):
if llil.operation in [
LowLevelILOperation.LLIL_CALL,
LowLevelILOperation.LLIL_CALL_STACK_ADJUST,
LowLevelILOperation.LLIL_JUMP,
LowLevelILOperation.LLIL_TAILCALL,
]:
if llil.dest.value.type not in [
RegisterValueType.ImportedAddressValue,
RegisterValueType.ConstantValue,
RegisterValueType.ConstantPointerValue,
]:
continue
address = llil.dest.value.value
candidate_addrs = [address]
stub_addr = is_stub_function(bv, address)
if stub_addr is not None:
candidate_addrs.append(stub_addr)
for address in candidate_addrs:
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
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:]):
yield API(name), ih.address
def extract_insn_number_features(
fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle
) -> Iterator[Tuple[Feature, Address]]:
"""
parse instruction number features
example:
push 3136B0h ; dwControlCode
"""
func: Function = fh.inner
results: List[Tuple[Any[Number, OperandNumber], Address]] = []
def llil_checker(il: LowLevelILInstruction, parent: LowLevelILInstruction, index: int) -> bool:
if il.operation == LowLevelILOperation.LLIL_LOAD:
return False
if il.operation not in [LowLevelILOperation.LLIL_CONST, LowLevelILOperation.LLIL_CONST_PTR]:
return True
for op in parent.operands:
if isinstance(op, ILRegister) and op.name in ["esp", "ebp", "rsp", "rbp", "sp"]:
return False
elif isinstance(op, LowLevelILInstruction) and op.operation == LowLevelILOperation.LLIL_REG:
if op.src.name in ["esp", "ebp", "rsp", "rbp", "sp"]:
return False
raw_value = il.value.value
if parent.operation == LowLevelILOperation.LLIL_SUB:
raw_value = -raw_value
results.append((Number(raw_value), ih.address))
results.append((OperandNumber(index, raw_value), ih.address))
return False
for llil in func.get_llils_at(ih.address):
visit_llil_exprs(llil, llil_checker)
yield from results
def extract_insn_bytes_features(fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle) -> Iterator[Tuple[Feature, Address]]:
"""
parse referenced byte sequences
example:
push offset iid_004118d4_IShellLinkA ; riid
"""
func: Function = fh.inner
bv: BinaryView = func.view
candidate_addrs = set()
llil = func.get_llil_at(ih.address)
if llil is None or llil.operation in [LowLevelILOperation.LLIL_CALL, LowLevelILOperation.LLIL_CALL_STACK_ADJUST]:
return
for ref in bv.get_code_refs_from(ih.address):
if ref == ih.address:
continue
if len(bv.get_functions_containing(ref)) > 0:
continue
candidate_addrs.add(ref)
# collect candidate address by enumerating all integers, https://github.com/Vector35/binaryninja-api/issues/3966
def llil_checker(il: LowLevelILInstruction, parent: LowLevelILInstruction, index: int) -> bool:
if il.operation in [LowLevelILOperation.LLIL_CONST, LowLevelILOperation.LLIL_CONST_PTR]:
value = il.value.value
if value > 0:
candidate_addrs.add(value)
return False
return True
for llil in func.get_llils_at(ih.address):
visit_llil_exprs(llil, llil_checker)
for addr in candidate_addrs:
extracted_bytes = bv.read(addr, MAX_BYTES_FEATURE_SIZE)
if extracted_bytes and not capa.features.extractors.helpers.all_zeros(extracted_bytes):
if bv.get_string_at(addr) is None:
# don't extract byte features for obvious strings
yield Bytes(extracted_bytes), ih.address
def extract_insn_string_features(
fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle
) -> Iterator[Tuple[Feature, Address]]:
"""
parse instruction string features
example:
push offset aAcr ; "ACR > "
"""
func: Function = fh.inner
bv: BinaryView = func.view
candidate_addrs = set()
# collect candidate address from code refs directly
for ref in bv.get_code_refs_from(ih.address):
if ref == ih.address:
continue
if len(bv.get_functions_containing(ref)) > 0:
continue
candidate_addrs.add(ref)
# collect candidate address by enumerating all integers, https://github.com/Vector35/binaryninja-api/issues/3966
def llil_checker(il: LowLevelILInstruction, parent: LowLevelILInstruction, index: int) -> bool:
if il.operation in [LowLevelILOperation.LLIL_CONST, LowLevelILOperation.LLIL_CONST_PTR]:
value = il.value.value
if value > 0:
candidate_addrs.add(value)
return False
return True
for llil in func.get_llils_at(ih.address):
visit_llil_exprs(llil, llil_checker)
# Now we have all the candidate address, check them for string or pointer to string
br = BinaryReader(bv)
for addr in candidate_addrs:
found = bv.get_string_at(addr)
if found:
yield String(found.value), ih.address
br.seek(addr)
pointer = None
if bv.arch.address_size == 4:
pointer = br.read32()
elif bv.arch.address_size == 8:
pointer = br.read64()
if pointer is not None:
found = bv.get_string_at(pointer)
if found:
yield String(found.value), ih.address
def extract_insn_offset_features(
fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle
) -> Iterator[Tuple[Feature, Address]]:
"""
parse instruction structure offset features
example:
.text:0040112F cmp [esi+4], ebx
"""
func: Function = fh.inner
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:
# The most common case, read/write dereference to something like `dword [eax+0x28]`
if il.operation in [LowLevelILOperation.LLIL_ADD, LowLevelILOperation.LLIL_SUB]:
left = il.left
right = il.right
# Exclude offsets based on stack/franme pointers
if left.operation == LowLevelILOperation.LLIL_REG and left.src.name in ["esp", "ebp", "rsp", "rbp", "sp"]:
return True
if right.operation != LowLevelILOperation.LLIL_CONST:
return True
raw_value = right.value.value
# If this is not a dereference, then this must be an add and the offset must be in the range \
# [0, MAX_STRUCTURE_SIZE]. For example,
# add eax, 0x10,
# lea ebx, [eax + 1]
if parent.operation not in [LowLevelILOperation.LLIL_LOAD, LowLevelILOperation.LLIL_STORE]:
if il.operation != LowLevelILOperation.LLIL_ADD or (not 0 < raw_value < MAX_STRUCTURE_SIZE):
return False
if address_size > 0:
# BN also encodes the constant value as two's complement, we need to restore its original value
value = capa.features.extractors.helpers.twos_complement(raw_value, address_size)
else:
value = raw_value
results.append((Offset(value), ih.address))
results.append((OperandOffset(index, value), ih.address))
return False
# An edge case: for code like `push dword [esi]`, we need to generate a feature for offset 0x0
elif il.operation in [LowLevelILOperation.LLIL_LOAD, LowLevelILOperation.LLIL_STORE]:
if il.operands[0].operation == LowLevelILOperation.LLIL_REG:
results.append((Offset(0), ih.address))
results.append((OperandOffset(index, 0), ih.address))
return False
return True
for llil in func.get_llils_at(ih.address):
visit_llil_exprs(llil, llil_checker)
yield from results
def is_nzxor_stack_cookie(f: Function, bb: BinjaBasicBlock, llil: LowLevelILInstruction) -> bool:
"""check if nzxor exists within stack cookie delta"""
# TODO(xusheng): use LLIL SSA to do more accurate analysis
# https://github.com/mandiant/capa/issues/1609
reg_names = []
if llil.left.operation == LowLevelILOperation.LLIL_REG:
reg_names.append(llil.left.src.name)
if llil.right.operation == LowLevelILOperation.LLIL_REG:
reg_names.append(llil.right.src.name)
# stack cookie reg should be stack/frame pointer
if not any(reg in ["ebp", "esp", "rbp", "rsp", "sp"] for reg in reg_names):
return False
# expect security cookie init in first basic block within first bytes (instructions)
if len(bb.incoming_edges) == 0 and llil.address < (bb.start + SECURITY_COOKIE_BYTES_DELTA):
return True
# ... or within last bytes (instructions) before a return
if len(bb.outgoing_edges) == 0 and llil.address > (bb.end - SECURITY_COOKIE_BYTES_DELTA):
return True
return False
def extract_insn_nzxor_characteristic_features(
fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle
) -> Iterator[Tuple[Feature, Address]]:
"""
parse instruction non-zeroing XOR instruction
ignore expected non-zeroing XORs, e.g. security cookies
"""
func: Function = fh.inner
results = []
def llil_checker(il: LowLevelILInstruction, parent: LowLevelILInstruction, index: int) -> bool:
# If the two operands of the xor instruction are the same, the LLIL will be translated to other instructions,
# 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):
return False
results.append((Characteristic("nzxor"), ih.address))
return False
else:
return True
for llil in func.get_llils_at(ih.address):
visit_llil_exprs(llil, llil_checker)
yield from results
def extract_insn_mnemonic_features(
fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle
) -> Iterator[Tuple[Feature, Address]]:
"""parse instruction mnemonic features"""
insn: DisassemblyInstruction = ih.inner
yield Mnemonic(insn.text[0].text), ih.address
def extract_insn_obfs_call_plus_5_characteristic_features(
fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle
) -> Iterator[Tuple[Feature, Address]]:
"""
parse call $+5 instruction from the given instruction.
"""
insn: DisassemblyInstruction = ih.inner
if insn.text[0].text == "call" and insn.text[2].text == "$+5" and insn.length == 5:
yield Characteristic("call $+5"), ih.address
def extract_insn_peb_access_characteristic_features(
fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle
) -> Iterator[Tuple[Feature, Address]]:
"""parse instruction peb access
fs:[0x30] on x86, gs:[0x60] on x64
"""
func: Function = fh.inner
results = []
def llil_checker(il: LowLevelILInstruction, parent: LowLevelILOperation, index: int) -> bool:
if il.operation != LowLevelILOperation.LLIL_LOAD:
return True
src = il.src
if src.operation != LowLevelILOperation.LLIL_ADD:
return True
left = src.left
right = src.right
if left.operation != LowLevelILOperation.LLIL_REG:
return True
reg = left.src.name
if right.operation != LowLevelILOperation.LLIL_CONST:
return True
value = right.value.value
if (reg, value) not in (("fsbase", 0x30), ("gsbase", 0x60)):
return True
results.append((Characteristic("peb access"), ih.address))
return False
for llil in func.get_llils_at(ih.address):
visit_llil_exprs(llil, llil_checker)
yield from results
def extract_insn_segment_access_features(
fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle
) -> Iterator[Tuple[Feature, Address]]:
"""parse instruction fs or gs access"""
func: Function = fh.inner
results = []
def llil_checker(il: LowLevelILInstruction, parent: LowLevelILInstruction, index: int) -> bool:
if il.operation == LowLevelILOperation.LLIL_REG:
reg = il.src.name
if reg == "fsbase":
results.append((Characteristic("fs access"), ih.address))
return False
elif reg == "gsbase":
results.append((Characteristic("gs access"), ih.address))
return False
return False
return True
for llil in func.get_llils_at(ih.address):
visit_llil_exprs(llil, llil_checker)
yield from results
def extract_insn_cross_section_cflow(
fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle
) -> Iterator[Tuple[Feature, Address]]:
"""inspect the instruction for a CALL or JMP that crosses section boundaries"""
func: Function = fh.inner
bv: BinaryView = func.view
if bv is None:
return
seg1 = bv.get_segment_at(ih.address)
sections1 = bv.get_sections_at(ih.address)
for ref in bv.get_code_refs_from(ih.address):
if len(bv.get_functions_at(ref)) == 0:
continue
seg2 = bv.get_segment_at(ref)
sections2 = bv.get_sections_at(ref)
if seg1 != seg2 or sections1 != sections2:
yield Characteristic("cross section flow"), 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
"""
func: Function = fh.inner
bv: BinaryView = func.view
if bv is None:
return
for il in func.get_llils_at(ih.address):
if il.operation not in [
LowLevelILOperation.LLIL_CALL,
LowLevelILOperation.LLIL_CALL_STACK_ADJUST,
LowLevelILOperation.LLIL_TAILCALL,
]:
continue
dest = il.dest
if dest.operation == LowLevelILOperation.LLIL_CONST_PTR:
value = dest.value.value
yield Characteristic("calls from"), AbsoluteVirtualAddress(value)
elif dest.operation == LowLevelILOperation.LLIL_CONST:
yield Characteristic("calls from"), AbsoluteVirtualAddress(dest.value)
elif dest.operation == LowLevelILOperation.LLIL_LOAD:
indirect_src = dest.src
if indirect_src.operation == LowLevelILOperation.LLIL_CONST_PTR:
value = indirect_src.value.value
yield Characteristic("calls from"), AbsoluteVirtualAddress(value)
elif indirect_src.operation == LowLevelILOperation.LLIL_CONST:
yield Characteristic("calls from"), AbsoluteVirtualAddress(indirect_src.value)
elif dest.operation == LowLevelILOperation.LLIL_REG:
if dest.value.type in [
RegisterValueType.ImportedAddressValue,
RegisterValueType.ConstantValue,
RegisterValueType.ConstantPointerValue,
]:
yield Characteristic("calls from"), AbsoluteVirtualAddress(dest.value.value)
def extract_function_indirect_call_characteristic_features(
fh: FunctionHandle, bbh: 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
"""
func: Function = fh.inner
llil = func.get_llil_at(ih.address)
if llil is None or llil.operation not in [
LowLevelILOperation.LLIL_CALL,
LowLevelILOperation.LLIL_CALL_STACK_ADJUST,
LowLevelILOperation.LLIL_TAILCALL,
]:
return
if llil.dest.operation in [LowLevelILOperation.LLIL_CONST, LowLevelILOperation.LLIL_CONST_PTR]:
return
if llil.dest.operation == LowLevelILOperation.LLIL_LOAD:
src = llil.dest.src
if src.operation in [LowLevelILOperation.LLIL_CONST, LowLevelILOperation.LLIL_CONST_PTR]:
return
yield Characteristic("indirect call"), ih.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):
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_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,
)

View File

@@ -0,0 +1,62 @@
# 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 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)
yield API(call.api), 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,145 @@
# 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 Dict, Tuple, 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)
# 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 Tuple, 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 Tuple, 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(
"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("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, Dict, List
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,446 @@
# 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, Dict, List, Union, Literal, Optional
from pydantic import Field, BaseModel, ConfigDict
from typing_extensions import Annotated, TypeAlias
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 its 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: str
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
class Process(ExactModel):
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, registery access.
# we'll detect the same with our API call analyis.
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[Cape] = None
dropped: Optional[List[File]] = None
procdump: Optional[List[ProcessFile]] = None
procmemory: ListTODO
# =========================================================================
# 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 List, Tuple, 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,12 @@
# 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 re
import logging
import binascii
import contextlib
@@ -10,12 +18,32 @@ import capa.features
import capa.features.extractors.elf
import capa.features.extractors.pefile
import capa.features.extractors.strings
from capa.features.common import OS, FORMAT_PE, FORMAT_ELF, OS_WINDOWS, FORMAT_FREEZE, Arch, Format, String, Feature
from capa.features.common import (
OS,
OS_ANY,
OS_AUTO,
ARCH_ANY,
FORMAT_PE,
FORMAT_ELF,
OS_WINDOWS,
FORMAT_FREEZE,
FORMAT_RESULT,
Arch,
Format,
String,
Feature,
)
from capa.features.freeze import is_freeze
from capa.features.address import NO_ADDRESS, Address, FileOffsetAddress
logger = logging.getLogger(__name__)
# match strings for formats
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]]:
"""
@@ -29,12 +57,19 @@ def extract_file_strings(buf, **kwargs) -> Iterator[Tuple[String, Address]]:
def extract_format(buf) -> Iterator[Tuple[Feature, Address]]:
if buf.startswith(b"MZ"):
if buf.startswith(MATCH_PE):
yield Format(FORMAT_PE), NO_ADDRESS
elif buf.startswith(b"\x7fELF"):
elif buf.startswith(MATCH_ELF):
yield Format(FORMAT_ELF), NO_ADDRESS
elif is_freeze(buf):
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)
@@ -45,10 +80,13 @@ def extract_format(buf) -> Iterator[Tuple[Feature, Address]]:
def extract_arch(buf) -> Iterator[Tuple[Feature, Address]]:
if buf.startswith(b"MZ"):
if buf.startswith(MATCH_PE):
yield from capa.features.extractors.pefile.extract_file_arch(pe=pefile.PE(data=buf))
elif buf.startswith(b"\x7fELF"):
elif buf.startswith(MATCH_RESULT):
yield Arch(ARCH_ANY), NO_ADDRESS
elif buf.startswith(MATCH_ELF):
with contextlib.closing(io.BytesIO(buf)) as f:
arch = capa.features.extractors.elf.detect_elf_arch(f)
@@ -73,10 +111,15 @@ def extract_arch(buf) -> Iterator[Tuple[Feature, Address]]:
return
def extract_os(buf) -> Iterator[Tuple[Feature, Address]]:
if buf.startswith(b"MZ"):
def extract_os(buf, os=OS_AUTO) -> Iterator[Tuple[Feature, Address]]:
if os != OS_AUTO:
yield OS(os), NO_ADDRESS
if buf.startswith(MATCH_PE):
yield OS(OS_WINDOWS), NO_ADDRESS
elif buf.startswith(b"\x7fELF"):
elif buf.startswith(MATCH_RESULT):
yield OS(OS_ANY), NO_ADDRESS
elif buf.startswith(MATCH_ELF):
with contextlib.closing(io.BytesIO(buf)) as f:
os = capa.features.extractors.elf.detect_elf_os(f)
@@ -92,8 +135,6 @@ def extract_os(buf) -> Iterator[Tuple[Feature, Address]]:
# 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.

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# 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
@@ -9,6 +9,7 @@
from __future__ import annotations
from typing import Dict, List, Tuple, Union, Iterator, Optional
from pathlib import Path
import dnfile
from dncil.cil.opcode import OpCodes
@@ -21,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,
@@ -52,25 +59,25 @@ class DnFileFeatureExtractorCache:
self.types[type_.token] = type_
def get_import(self, token: int) -> Optional[Union[DnType, DnUnmanagedMethod]]:
return self.imports.get(token, None)
return self.imports.get(token)
def get_native_import(self, token: int) -> Optional[Union[DnType, DnUnmanagedMethod]]:
return self.native_imports.get(token, None)
return self.native_imports.get(token)
def get_method(self, token: int) -> Optional[Union[DnType, DnUnmanagedMethod]]:
return self.methods.get(token, None)
return self.methods.get(token)
def get_field(self, token: int) -> Optional[Union[DnType, DnUnmanagedMethod]]:
return self.fields.get(token, None)
return self.fields.get(token)
def get_type(self, token: int) -> Optional[Union[DnType, DnUnmanagedMethod]]:
return self.types.get(token, None)
return self.types.get(token)
class DnfileFeatureExtractor(FeatureExtractor):
def __init__(self, path: str):
super().__init__()
self.pe: dnfile.dnPE = dnfile.dnPE(path)
class DnfileFeatureExtractor(StaticFeatureExtractor):
def __init__(self, path: Path):
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
@@ -119,7 +126,7 @@ class DnfileFeatureExtractor(FeatureExtractor):
address: DNTokenAddress = DNTokenAddress(insn.operand.value)
# record call to destination method; note: we only consider MethodDef methods for destinations
dest: Optional[FunctionHandle] = methods.get(address, None)
dest: Optional[FunctionHandle] = methods.get(address)
if dest is not None:
dest.ctx["calls_to"].add(fh.address)

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# 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

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# 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

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# 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
@@ -52,7 +52,7 @@ def resolve_dotnet_token(pe: dnfile.dnPE, token: Token) -> Union[dnfile.base.MDT
return InvalidToken(token.value)
return user_string
table: Optional[dnfile.base.ClrMetaDataTable] = pe.net.mdtables.tables.get(token.table, None)
table: Optional[dnfile.base.ClrMetaDataTable] = pe.net.mdtables.tables.get(token.table)
if table is None:
# table index is not valid
return InvalidToken(token.value)
@@ -131,10 +131,14 @@ def get_dotnet_managed_imports(pe: dnfile.dnPE) -> Iterator[DnType]:
# 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,
)
@@ -188,6 +192,8 @@ 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)
"""
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
@@ -204,14 +210,16 @@ def get_dotnet_managed_methods(pe: dnfile.dnPE) -> Iterator[DnType]:
continue
token: int = calculate_dotnet_token_value(method.table.number, method.row_index)
access: Optional[str] = accessor_map.get(token, None)
access: Optional[str] = accessor_map.get(token)
method_name: 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,8 +245,11 @@ 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]]:
@@ -300,19 +313,119 @@ 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
try:
table = pe.net.mdtables.tables.get(table_index, [])
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 = 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 typedef.TypeNamespace, tuple(typedef_name[::-1])
name = 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 typedef.TypeNamespace, tuple(typedef_name[::-1])
enclosing_name = table_row.TypeName
typedef_name.append(enclosing_name)
return table_row.TypeNamespace, tuple(typedef_name[::-1])
else:
return typedef.TypeNamespace, (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 = 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 typeref.TypeNamespace, (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 = 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 typeref.TypeNamespace, tuple(typeref_name[::-1])
# Document the root enclosing details
typeref_name.append(table_row.TypeName)
return table_row.TypeNamespace, tuple(typeref_name[::-1])
else:
return typeref.TypeNamespace, (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:

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# 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
@@ -9,7 +9,7 @@
from __future__ import annotations
import logging
from typing import TYPE_CHECKING, Any, Dict, Tuple, Union, Iterator, Optional
from typing import TYPE_CHECKING, Tuple, Union, Iterator, Optional
if TYPE_CHECKING:
from capa.features.extractors.dnfile.extractor import DnFileFeatureExtractorCache

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# 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
@@ -6,16 +6,17 @@
# 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 typing import Union, Optional
from typing import Tuple, Optional
class DnType(object):
def __init__(self, token: int, class_: str, namespace: str = "", member: str = "", access: Optional[str] = None):
class DnType:
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"
@@ -43,9 +44,13 @@ class DnType(object):
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,150 +0,0 @@
import logging
from typing import Tuple, Iterator
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: str):
super().__init__()
self.path: str = path
self.pe: dnfile.dnPE = dnfile.dnPE(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,5 +1,13 @@
# 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, cast
from typing import Tuple, Iterator
from pathlib import Path
import dnfile
import pefile
@@ -23,15 +31,18 @@ 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__)
@@ -49,7 +60,7 @@ def extract_file_import_names(pe: dnfile.dnPE, **kwargs) -> Iterator[Tuple[Impor
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)
@@ -84,19 +95,25 @@ def extract_file_namespace_features(pe: dnfile.dnPE, **kwargs) -> Iterator[Tuple
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]]:
@@ -157,11 +174,11 @@ GLOBAL_HANDLERS = (
)
class DotnetFileFeatureExtractor(FeatureExtractor):
def __init__(self, path: str):
super().__init__()
self.path: str = path
self.pe: dnfile.dnPE = dnfile.dnPE(path)
class DotnetFileFeatureExtractor(StaticFeatureExtractor):
def __init__(self, path: Path):
super().__init__(hashes=SampleHashes.from_bytes(path.read_bytes()))
self.path: Path = path
self.pe: dnfile.dnPE = dnfile.dnPE(str(path))
def get_base_address(self):
return NO_ADDRESS

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# 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
@@ -13,6 +13,8 @@ from enum import Enum
from typing import Set, Dict, List, Tuple, BinaryIO, Iterator, Optional
from dataclasses import dataclass
import Elf # from vivisect
logger = logging.getLogger(__name__)
@@ -24,7 +26,7 @@ def align(v, alignment):
return v + (alignment - remainder)
def read_cstr(buf, offset):
def read_cstr(buf, offset) -> str:
s = buf[offset:]
s, _, _ = s.partition(b"\x00")
return s.decode("utf-8")
@@ -54,6 +56,7 @@ class OS(str, Enum):
CLOUD = "cloud"
SYLLABLE = "syllable"
NACL = "nacl"
ANDROID = "android"
# via readelf: https://github.com/bminor/binutils-gdb/blob/c0e94211e1ac05049a4ce7c192c9d14d1764eb3e/binutils/readelf.c#L19635-L19658
@@ -88,8 +91,26 @@ class Shdr:
offset: int
size: int
link: int
entsize: int
buf: bytes
@classmethod
def from_viv(cls, section, buf: bytes) -> "Shdr":
return cls(
section.sh_name,
section.sh_type,
section.sh_flags,
section.sh_addr,
section.sh_offset,
section.sh_size,
section.sh_link,
section.sh_entsize,
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):
@@ -102,6 +123,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
@@ -121,23 +143,27 @@ class ELF:
elif ei_class == 2:
self.bitness = 64
else:
raise CorruptElfFile("invalid ei_class: 0x%02x" % ei_class)
raise CorruptElfFile(f"invalid ei_class: 0x{ei_class:02x}")
if ei_data == 1:
self.endian = "<"
elif ei_data == 2:
self.endian = ">"
else:
raise CorruptElfFile("not an ELF file: invalid ei_data: 0x%02x" % ei_data)
raise CorruptElfFile(f"not an ELF file: invalid ei_data: 0x{ei_data:02x}")
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()
@@ -320,12 +346,12 @@ class ELF:
shent = self.shbuf[shent_offset : shent_offset + self.e_shentsize]
if self.bitness == 32:
sh_name, sh_type, sh_flags, sh_addr, sh_offset, sh_size, sh_link = struct.unpack_from(
self.endian + "IIIIIII", shent, 0x0
sh_name, sh_type, sh_flags, sh_addr, sh_offset, sh_size, sh_link, _, _, sh_entsize = struct.unpack_from(
self.endian + "IIIIIIIIII", shent, 0x0
)
elif self.bitness == 64:
sh_name, sh_type, sh_flags, sh_addr, sh_offset, sh_size, sh_link = struct.unpack_from(
self.endian + "IIQQQQI", shent, 0x0
sh_name, sh_type, sh_flags, sh_addr, sh_offset, sh_size, sh_link, _, _, sh_entsize = struct.unpack_from(
self.endian + "IIQQQQIIQQ", shent, 0x0
)
else:
raise NotImplementedError()
@@ -337,7 +363,7 @@ class ELF:
if len(buf) != sh_size:
raise ValueError("failed to read section header content")
return Shdr(sh_name, sh_type, sh_flags, sh_addr, sh_offset, sh_size, sh_link, buf)
return Shdr(sh_name, sh_type, sh_flags, sh_addr, sh_offset, sh_size, sh_link, sh_entsize, buf)
@property
def section_headers(self):
@@ -347,6 +373,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
@@ -396,7 +426,7 @@ class ELF:
# there should be vn_cnt of these.
# each entry describes an ABI name required by the shared object.
vna_offset = vn_offset + vn_aux
for i in range(vn_cnt):
for _ in range(vn_cnt):
# ElfXX_Vernaux layout is the same on 32 and 64 bit
_, _, _, vna_name, vna_next = struct.unpack_from(self.endian + "IHHII", shdr.buf, vna_offset)
@@ -457,10 +487,12 @@ class ELF:
for d_tag, d_val in self.dynamic_entries:
if d_tag == DT_STRTAB:
strtab_addr = d_val
break
for d_tag, d_val in self.dynamic_entries:
if d_tag == DT_STRSZ:
strtab_size = d_val
break
if strtab_addr is None:
return None
@@ -470,8 +502,10 @@ class ELF:
strtab_offset = None
for shdr in self.section_headers:
if shdr.addr <= strtab_addr < shdr.addr + shdr.size:
# the section header address should be defined
if shdr.addr and shdr.addr <= strtab_addr < shdr.addr + shdr.size:
strtab_offset = shdr.offset + (strtab_addr - shdr.addr)
break
if strtab_offset is None:
return None
@@ -500,7 +534,27 @@ class ELF:
if d_tag != DT_NEEDED:
continue
yield read_cstr(strtab, d_val)
try:
yield read_cstr(strtab, d_val)
except UnicodeDecodeError as e:
logger.warning("failed to read DT_NEEDED entry: %s", str(e))
@property
def symtab(self) -> Optional[Tuple[Shdr, Shdr]]:
"""
fetch the Shdr for the symtab and the associated strtab.
"""
SHT_SYMTAB = 0x2
for shdr in self.section_headers:
if shdr.type != SHT_SYMTAB:
continue
# the linked section contains strings referenced by the symtab structures.
strtab_shdr = self.parse_section_header(shdr.link)
return shdr, strtab_shdr
return None
@dataclass
@@ -603,11 +657,101 @@ class SHNote:
return ABITag(os, kmajor, kminor, kpatch)
def guess_os_from_osabi(elf) -> Optional[OS]:
@dataclass
class Symbol:
name_offset: int
value: int
size: int
info: int
other: int
shndx: int
class SymTab:
def __init__(
self,
endian: str,
bitness: int,
symtab: Shdr,
strtab: Shdr,
) -> None:
self.symbols: List[Symbol] = []
self.symtab = symtab
self.strtab = strtab
self._parse(endian, bitness, symtab.buf)
def _parse(self, endian: str, bitness: int, symtab_buf: bytes) -> None:
"""
return the symbol's information in
the order specified by sys/elf32.h
"""
if self.symtab.entsize == 0:
return
for i in range(int(len(self.symtab.buf) / self.symtab.entsize)):
if bitness == 32:
name_offset, value, size, info, other, shndx = struct.unpack_from(
endian + "IIIBBH", symtab_buf, i * self.symtab.entsize
)
elif bitness == 64:
name_offset, info, other, shndx, value, size = struct.unpack_from(
endian + "IBBHQQ", symtab_buf, i * self.symtab.entsize
)
self.symbols.append(Symbol(name_offset, value, size, info, other, shndx))
def get_name(self, symbol: Symbol) -> str:
"""
fetch a symbol's name from symtab's
associated strings' section (SHT_STRTAB)
"""
if not self.strtab:
raise ValueError("no strings found")
for i in range(symbol.name_offset, self.strtab.size):
if self.strtab.buf[i] == 0:
return self.strtab.buf[symbol.name_offset : i].decode("utf-8")
raise ValueError("symbol name not found")
def get_symbols(self) -> Iterator[Symbol]:
"""
return a tuple: (name, value, size, info, other, shndx)
for each symbol contained in the symbol table
"""
yield from self.symbols
@classmethod
def from_viv(cls, elf: Elf.Elf) -> Optional["SymTab"]:
endian = "<" if elf.getEndian() == 0 else ">"
bitness = elf.bits
SHT_SYMTAB = 0x2
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, elf.readAtOffset(strtab_section.sh_offset, strtab_section.sh_size)
)
try:
return cls(endian, bitness, sh_symtab, sh_strtab)
except NameError:
return None
except Exception:
# all exceptions that could be encountered by
# cls._parse() imply a faulty symbol's table.
raise CorruptElfFile("malformed symbol's table")
def guess_os_from_osabi(elf: ELF) -> Optional[OS]:
return elf.ei_osabi
def guess_os_from_ph_notes(elf) -> Optional[OS]:
def guess_os_from_ph_notes(elf: ELF) -> Optional[OS]:
# search for PT_NOTE sections that specify an OS
# for example, on Linux there is a GNU section with minimum kernel version
PT_NOTE = 0x4
@@ -635,6 +779,11 @@ def guess_os_from_ph_notes(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:
@@ -646,7 +795,7 @@ def guess_os_from_ph_notes(elf) -> Optional[OS]:
return None
def guess_os_from_sh_notes(elf) -> Optional[OS]:
def guess_os_from_sh_notes(elf: ELF) -> Optional[OS]:
# search for notes stored in sections that aren't visible in program headers.
# e.g. .note.Linux in Linux kernel modules.
SHT_NOTE = 0x7
@@ -679,7 +828,49 @@ def guess_os_from_sh_notes(elf) -> Optional[OS]:
return None
def guess_os_from_linker(elf) -> Optional[OS]:
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
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
linker = elf.linker
@@ -689,12 +880,12 @@ def guess_os_from_linker(elf) -> Optional[OS]:
return None
def guess_os_from_abi_versions_needed(elf) -> Optional[OS]:
def guess_os_from_abi_versions_needed(elf: ELF) -> Optional[OS]:
# then lets look for GLIBC symbol versioning requirements.
# this will let us guess about linux/hurd in some cases.
versions_needed = elf.versions_needed
if any(map(lambda abi: abi.startswith("GLIBC"), itertools.chain(*versions_needed.values()))):
if any(abi.startswith("GLIBC") for abi in itertools.chain(*versions_needed.values())):
# there are any GLIBC versions needed
if elf.e_machine != "i386":
@@ -714,51 +905,128 @@ def guess_os_from_abi_versions_needed(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
def guess_os_from_needed_dependencies(elf) -> Optional[OS]:
def guess_os_from_needed_dependencies(elf: ELF) -> Optional[OS]:
for needed in elf.needed:
if needed.startswith("libmachuser.so"):
return OS.HURD
if needed.startswith("libhurduser.so"):
return OS.HURD
if needed.startswith("libandroid.so"):
return OS.ANDROID
return None
def guess_os_from_symtab(elf: ELF) -> Optional[OS]:
shdrs = elf.symtab
if not shdrs:
# executable does not contain a symbol table
# or the symbol's names are stripped
return None
symtab_shdr, strtab_shdr = shdrs
symtab = SymTab(elf.endian, elf.bitness, symtab_shdr, strtab_shdr)
keywords = {
OS.LINUX: [
"linux",
"/linux/",
],
}
for symbol in symtab.get_symbols():
sym_name = symtab.get_name(symbol)
for os, hints in keywords.items():
if any(hint in sym_name for hint in hints):
return os
return None
def detect_elf_os(f) -> str:
"""
f: type Union[BinaryIO, IDAIO]
f: type Union[BinaryIO, IDAIO, GHIDRAIO]
"""
elf = ELF(f)
try:
elf = ELF(f)
except Exception as e:
logger.warning("Error parsing ELF file: %s", e)
return "unknown"
osabi_guess = guess_os_from_osabi(elf)
logger.debug("guess: osabi: %s", osabi_guess)
try:
osabi_guess = guess_os_from_osabi(elf)
logger.debug("guess: osabi: %s", osabi_guess)
except Exception as e:
logger.warning("Error guessing OS from OSABI: %s", e)
osabi_guess = None
ph_notes_guess = guess_os_from_ph_notes(elf)
logger.debug("guess: ph notes: %s", ph_notes_guess)
try:
ph_notes_guess = guess_os_from_ph_notes(elf)
logger.debug("guess: ph notes: %s", ph_notes_guess)
except Exception as e:
logger.warning("Error guessing OS from program header notes: %s", e)
ph_notes_guess = None
sh_notes_guess = guess_os_from_sh_notes(elf)
logger.debug("guess: sh notes: %s", sh_notes_guess)
try:
sh_notes_guess = guess_os_from_sh_notes(elf)
logger.debug("guess: sh notes: %s", sh_notes_guess)
except Exception as e:
logger.warning("Error guessing OS from section header notes: %s", e)
sh_notes_guess = None
linker_guess = guess_os_from_linker(elf)
logger.debug("guess: linker: %s", linker_guess)
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
abi_versions_needed_guess = guess_os_from_abi_versions_needed(elf)
logger.debug("guess: ABI versions needed: %s", abi_versions_needed_guess)
try:
linker_guess = guess_os_from_linker(elf)
logger.debug("guess: linker: %s", linker_guess)
except Exception as e:
logger.warning("Error guessing OS from linker: %s", e)
linker_guess = None
needed_dependencies_guess = guess_os_from_needed_dependencies(elf)
logger.debug("guess: needed dependencies: %s", needed_dependencies_guess)
try:
abi_versions_needed_guess = guess_os_from_abi_versions_needed(elf)
logger.debug("guess: ABI versions needed: %s", abi_versions_needed_guess)
except Exception as e:
logger.warning("Error guessing OS from ABI versions needed: %s", e)
abi_versions_needed_guess = None
try:
needed_dependencies_guess = guess_os_from_needed_dependencies(elf)
logger.debug("guess: needed dependencies: %s", needed_dependencies_guess)
except Exception as e:
logger.warning("Error guessing OS from needed dependencies: %s", e)
needed_dependencies_guess = None
try:
symtab_guess = guess_os_from_symtab(elf)
logger.debug("guess: pertinent symbol name: %s", symtab_guess)
except Exception as e:
logger.warning("Error guessing OS from symbol table: %s", e)
symtab_guess = None
ret = None
if osabi_guess:
ret = osabi_guess
elif ident_guess:
# we don't trust this too much due to non-cross-compilation assumptions
ret = ident_guess
elif ph_notes_guess:
ret = ph_notes_guess
@@ -774,6 +1042,9 @@ def detect_elf_os(f) -> str:
elif needed_dependencies_guess:
ret = needed_dependencies_guess
elif symtab_guess:
ret = symtab_guess
return ret.value if ret is not None else "unknown"

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# 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
@@ -8,23 +8,22 @@
import io
import logging
from typing import Tuple, Iterator
from pathlib import Path
from elftools.elf.elffile import ELFFile, SymbolTableSection
from elftools.elf.relocation import RelocationSection
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
@@ -34,14 +33,64 @@ def extract_file_import_names(elf, **kwargs):
logger.debug("Symbol table '%s' contains %s entries:", section.name, section.num_symbols())
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)
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 section in elf.iter_sections():
if not isinstance(section, SymbolTableSection):
continue
for _, symbol in enumerate(section.iter_symbols()):
if symbol.name and symbol.entry.st_info.type == "STT_FUNC":
# TODO symbol address
# TODO symbol version info?
yield Import(symbol.name), FileOffsetAddress(0x0)
# 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 section in elf.iter_sections():
if not isinstance(section, RelocationSection):
continue
if section["sh_entsize"] == 0:
logger.debug("Symbol table '%s' has a sh_entsize of zero!", section.name)
continue
logger.debug("Symbol table '%s' contains %s entries:", section.name, section.num_relocations())
for relocation in section.iter_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, **kwargs):
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)
@@ -53,7 +102,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:
@@ -67,8 +116,7 @@ def extract_file_format(**kwargs):
yield Format(FORMAT_ELF), NO_ADDRESS
def extract_file_arch(elf, **kwargs):
# TODO merge with capa.features.extractors.elf.detect_elf_arch()
def extract_file_arch(elf: ELFFile, **kwargs):
arch = elf.get_machine_arch()
if arch == "x86":
yield Arch("i386"), NO_ADDRESS
@@ -85,7 +133,7 @@ def extract_file_features(elf: ELFFile, buf: bytes) -> Iterator[Tuple[Feature, i
FILE_HANDLERS = (
# TODO extract_file_export_names,
extract_file_export_names,
extract_file_import_names,
extract_file_section_names,
extract_file_strings,
@@ -106,12 +154,11 @@ GLOBAL_HANDLERS = (
)
class ElfFeatureExtractor(FeatureExtractor):
def __init__(self, path: str):
super().__init__()
self.path = path
with open(self.path, "rb") as f:
self.elf = ELFFile(io.BytesIO(f.read()))
class ElfFeatureExtractor(StaticFeatureExtractor):
def __init__(self, path: Path):
super().__init__(SampleHashes.from_bytes(path.read_bytes()))
self.path: Path = path
self.elf = ELFFile(io.BytesIO(path.read_bytes()))
def get_base_address(self):
# virtual address of the first segment with type LOAD
@@ -120,15 +167,13 @@ class ElfFeatureExtractor(FeatureExtractor):
return AbsoluteVirtualAddress(segment.header.p_vaddr)
def extract_global_features(self):
with open(self.path, "rb") as f:
buf = f.read()
buf = self.path.read_bytes()
for feature, addr in extract_global_features(self.elf, buf):
yield feature, addr
def extract_file_features(self):
with open(self.path, "rb") as f:
buf = f.read()
buf = self.path.read_bytes()
for feature, addr in extract_file_features(self.elf, buf):
yield feature, addr

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 Tuple, 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 List, Tuple, 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 its 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 List, Tuple, 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 Tuple, 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 Tuple, 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 Dict, List, 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 poin 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, Dict, Tuple, 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()

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# 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
@@ -41,35 +41,64 @@ def is_ordinal(symbol: str) -> bool:
return False
def generate_symbols(dll: str, symbol: str) -> Iterator[str]:
def generate_symbols(dll: str, symbol: str, include_dll=False) -> Iterator[str]:
"""
for a given dll and symbol name, generate variants.
we over-generate features to make matching easier.
these include:
- kernel32.CreateFileA
- kernel32.CreateFile
- CreateFileA
- CreateFile
- ws2_32.#1
note that since capa v7 only `import` features and APIs called via ordinal include DLL names:
- kernel32.CreateFileA
- kernel32.CreateFile
- ws2_32.#1
for `api` features dll names are good for documentation but not used during matching
"""
# normalize dll name
dll = dll.lower()
# kernel32.CreateFileA
yield "%s.%s" % (dll, symbol)
# trim extensions observed in dynamic traces
dll = dll[0:-4] if dll.endswith(".dll") else dll
dll = dll[0:-4] if dll.endswith(".drv") else dll
if include_dll or is_ordinal(symbol):
# ws2_32.#1
# kernel32.CreateFileA
yield f"{dll}.{symbol}"
if not is_ordinal(symbol):
# CreateFileA
yield symbol
if is_aw_function(symbol):
# kernel32.CreateFile
yield "%s.%s" % (dll, symbol[:-1])
if is_aw_function(symbol):
if include_dll:
# kernel32.CreateFile
yield f"{dll}.{symbol[:-1]}"
if not is_ordinal(symbol):
# CreateFile
yield symbol[:-1]
def reformat_forwarded_export_name(forwarded_name: str) -> str:
"""
a forwarded export has a DLL name/path and symbol name.
we want the former to be lowercase, and the latter to be verbatim.
"""
# use rpartition so we can split on separator between dll and name.
# the dll name can be a full path, like in the case of
# ef64d6d7c34250af8e21a10feb931c9b
# which i assume means the path can have embedded periods.
# so we don't want the first period, we want the last.
forwarded_dll, _, forwarded_symbol = forwarded_name.rpartition(".")
forwarded_dll = forwarded_dll.lower()
return f"{forwarded_dll}.{forwarded_symbol}"
def all_zeros(bytez: bytes) -> bool:
return all(b == 0 for b in builtins.bytes(bytez))

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# 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
@@ -34,7 +34,7 @@ def get_printable_len(op: idaapi.op_t) -> int:
elif op.dtype == idaapi.dt_qword:
chars = struct.pack("<Q", op_val)
else:
raise ValueError("Unhandled operand data type 0x%x." % op.dtype)
raise ValueError(f"Unhandled operand data type 0x{op.dtype:x}.")
def is_printable_ascii(chars_: bytes):
return all(c < 127 and chr(c) in string.printable for c in chars_)
@@ -104,19 +104,3 @@ BASIC_BLOCK_HANDLERS = (
extract_bb_tight_loop,
extract_bb_stackstring,
)
def main():
features = []
for fhandle in helpers.get_functions(skip_thunks=True, skip_libs=True):
f: idaapi.func_t = fhandle.inner
for bb in idaapi.FlowChart(f, flags=idaapi.FC_PREDS):
features.extend(list(extract_features(fhandle, bb)))
import pprint
pprint.pprint(features)
if __name__ == "__main__":
main()

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# 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
@@ -8,6 +8,7 @@
from typing import List, Tuple, Iterator
import idaapi
import ida_nalt
import capa.ida.helpers
import capa.features.extractors.elf
@@ -18,12 +19,22 @@ import capa.features.extractors.ida.function
import capa.features.extractors.ida.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 IdaFeatureExtractor(FeatureExtractor):
class IdaFeatureExtractor(StaticFeatureExtractor):
def __init__(self):
super().__init__()
super().__init__(
hashes=SampleHashes(
md5=ida_nalt.retrieve_input_file_md5(), sha1="(unknown)", sha256=ida_nalt.retrieve_input_file_sha256()
)
)
self.global_features: List[Tuple[Feature, Address]] = []
self.global_features.extend(capa.features.extractors.ida.file.extract_file_format())
self.global_features.extend(capa.features.extractors.ida.global_.extract_os())

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# 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
@@ -12,6 +12,7 @@ from typing import Tuple, Iterator
import idc
import idaapi
import idautils
import ida_entry
import capa.features.extractors.common
import capa.features.extractors.helpers
@@ -21,12 +22,14 @@ 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 check_segment_for_pe(seg: idaapi.segment_t) -> Iterator[Tuple[int, int]]:
"""check segment for embedded PE
adapted for IDA from:
https://github.com/vivisect/vivisect/blob/7be4037b1cecc4551b397f840405a1fc606f9b53/PE/carve.py#L19
https://github.com/vivisect/vivisect/blob/91e8419a861f49779f18316f155311967e696836/PE/carve.py#L25
"""
seg_max = seg.end_ea
mz_xor = [
@@ -40,13 +43,14 @@ def check_segment_for_pe(seg: idaapi.segment_t) -> Iterator[Tuple[int, int]]:
todo = []
for mzx, pex, i in mz_xor:
# find all segment offsets containing XOR'd "MZ" bytes
for off in capa.features.extractors.ida.helpers.find_byte_sequence(seg.start_ea, seg.end_ea, mzx):
todo.append((off, mzx, pex, i))
while len(todo):
off, mzx, pex, i = todo.pop()
# The MZ header has one field we will check e_lfanew is at 0x3c
# MZ header has one field we will check e_lfanew is at 0x3c
e_lfanew = off + 0x3C
if seg_max < (e_lfanew + 4):
@@ -54,6 +58,10 @@ def check_segment_for_pe(seg: idaapi.segment_t) -> Iterator[Tuple[int, int]]:
newoff = struct.unpack("<I", capa.features.extractors.helpers.xor_static(idc.get_bytes(e_lfanew, 4), 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
@@ -61,9 +69,6 @@ def check_segment_for_pe(seg: idaapi.segment_t) -> Iterator[Tuple[int, int]]:
if idc.get_bytes(peoff, 2) == pex:
yield off, i
for nextres in capa.features.extractors.ida.helpers.find_byte_sequence(off + 1, seg.end_ea, mzx):
todo.append((nextres, mzx, pex, i))
def extract_file_embedded_pe() -> Iterator[Tuple[Feature, Address]]:
"""extract embedded PE features
@@ -79,8 +84,14 @@ def extract_file_embedded_pe() -> Iterator[Tuple[Feature, Address]]:
def extract_file_export_names() -> Iterator[Tuple[Feature, Address]]:
"""extract function exports"""
for _, _, ea, name in idautils.Entries():
yield Export(name), AbsoluteVirtualAddress(ea)
for _, ordinal, ea, name in idautils.Entries():
forwarded_name = ida_entry.get_entry_forwarder(ordinal)
if forwarded_name is None:
yield Export(name), AbsoluteVirtualAddress(ea)
else:
forwarded_name = capa.features.extractors.helpers.reformat_forwarded_export_name(forwarded_name)
yield Export(forwarded_name), AbsoluteVirtualAddress(ea)
yield Characteristic("forwarded export"), AbsoluteVirtualAddress(ea)
def extract_file_import_names() -> Iterator[Tuple[Feature, Address]]:
@@ -99,20 +110,20 @@ def extract_file_import_names() -> Iterator[Tuple[Feature, Address]]:
if info[1] and info[2]:
# e.g. in mimikatz: ('cabinet', 'FCIAddFile', 11L)
# extract by name here and by ordinal below
for name in capa.features.extractors.helpers.generate_symbols(info[0], info[1]):
for name in capa.features.extractors.helpers.generate_symbols(info[0], info[1], include_dll=True):
yield Import(name), addr
dll = info[0]
symbol = "#%d" % (info[2])
symbol = f"#{info[2]}"
elif info[1]:
dll = info[0]
symbol = info[1]
elif info[2]:
dll = info[0]
symbol = "#%d" % (info[2])
symbol = f"#{info[2]}"
else:
continue
for name in capa.features.extractors.helpers.generate_symbols(dll, symbol):
for name in capa.features.extractors.helpers.generate_symbols(dll, symbol, include_dll=True):
yield Import(name), addr
for ea, info in capa.features.extractors.ida.helpers.get_file_externs().items():
@@ -176,7 +187,7 @@ def extract_file_format() -> Iterator[Tuple[Feature, Address]]:
# no file type to return when processing a binary file, but we want to continue processing
return
else:
raise NotImplementedError("unexpected file format: %d" % file_info.filetype)
raise NotImplementedError(f"unexpected file format: {file_info.filetype}")
def extract_features() -> Iterator[Tuple[Feature, Address]]:
@@ -195,14 +206,3 @@ FILE_HANDLERS = (
extract_file_function_names,
extract_file_format,
)
def main():
""" """
import pprint
pprint.pprint(list(extract_features()))
if __name__ == "__main__":
main()

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# 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
@@ -50,18 +50,3 @@ def extract_features(fh: FunctionHandle) -> Iterator[Tuple[Feature, Address]]:
FUNCTION_HANDLERS = (extract_function_calls_to, extract_function_loop, extract_recursive_call)
def main():
""" """
features = []
for fhandle in capa.features.extractors.ida.helpers.get_functions(skip_thunks=True, skip_libs=True):
features.extend(list(extract_features(fhandle)))
import pprint
pprint.pprint(features)
if __name__ == "__main__":
main()

View File

@@ -1,3 +1,10 @@
# 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 Tuple, Iterator

View File

@@ -1,10 +1,11 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# 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 functools
from typing import Any, Dict, Tuple, Iterator, Optional
import idc
@@ -25,9 +26,10 @@ def find_byte_sequence(start: int, end: int, seq: bytes) -> Iterator[int]:
end: max virtual address
seq: bytes to search e.g. b"\x01\x03"
"""
seqstr = " ".join(["%02x" % b for b in seq])
seqstr = " ".join([f"{b:02x}" for b in seq])
while True:
# TODO find_binary: Deprecated. Please use ida_bytes.bin_search() instead.
# TODO(mike-hunhoff): find_binary is deprecated. Please use ida_bytes.bin_search() instead.
# https://github.com/mandiant/capa/issues/1606
ea = idaapi.find_binary(start, end, seqstr, 0, idaapi.SEARCH_DOWN)
if ea == idaapi.BADADDR:
break
@@ -80,9 +82,22 @@ def get_segment_buffer(seg: idaapi.segment_t) -> bytes:
return buff if buff else b""
def inspect_import(imports, library, ea, function, ordinal):
if function and function.startswith("__imp_"):
# handle mangled PE imports
function = function[len("__imp_") :]
if function and "@@" in function:
# handle mangled ELF imports, like "fopen@@glibc_2.2.5"
function, _, _ = function.partition("@@")
imports[ea] = (library.lower(), function, ordinal)
return True
def get_file_imports() -> Dict[int, Tuple[str, str, int]]:
"""get file imports"""
imports = {}
imports: Dict[int, Tuple[str, str, int]] = {}
for idx in range(idaapi.get_import_module_qty()):
library = idaapi.get_import_module_name(idx)
@@ -90,22 +105,15 @@ def get_file_imports() -> Dict[int, Tuple[str, str, int]]:
if not library:
continue
# IDA uses section names for the library of ELF imports, like ".dynsym"
library = library.lstrip(".")
# IDA uses section names for the library of ELF imports, like ".dynsym".
# These are not useful to us, we may need to expand this list over time
# TODO(williballenthin): find all section names used by IDA
# https://github.com/mandiant/capa/issues/1419
if library == ".dynsym":
library = ""
def inspect_import(ea, function, ordinal):
if function and function.startswith("__imp_"):
# handle mangled PE imports
function = function[len("__imp_") :]
if function and "@@" in function:
# handle mangled ELF imports, like "fopen@@glibc_2.2.5"
function, _, _ = function.partition("@@")
imports[ea] = (library.lower(), function, ordinal)
return True
idaapi.enum_import_names(idx, inspect_import)
cb = functools.partial(inspect_import, imports, library)
idaapi.enum_import_names(idx, cb)
return imports
@@ -114,7 +122,7 @@ def get_file_externs() -> Dict[int, Tuple[str, str, int]]:
externs = {}
for seg in get_segments(skip_header_segments=True):
if not (seg.type == ida_segment.SEG_XTRN):
if seg.type != ida_segment.SEG_XTRN:
continue
for ea in idautils.Functions(seg.start_ea, seg.end_ea):
@@ -267,20 +275,18 @@ def is_op_offset(insn: idaapi.insn_t, op: idaapi.op_t) -> bool:
def is_sp_modified(insn: idaapi.insn_t) -> bool:
"""determine if instruction modifies SP, ESP, RSP"""
for op in get_insn_ops(insn, target_ops=(idaapi.o_reg,)):
if op.reg == idautils.procregs.sp.reg and is_op_write(insn, op):
# register is stack and written
return True
return False
return any(
op.reg == idautils.procregs.sp.reg and is_op_write(insn, op)
for op in get_insn_ops(insn, target_ops=(idaapi.o_reg,))
)
def is_bp_modified(insn: idaapi.insn_t) -> bool:
"""check if instruction modifies BP, EBP, RBP"""
for op in get_insn_ops(insn, target_ops=(idaapi.o_reg,)):
if op.reg == idautils.procregs.bp.reg and is_op_write(insn, op):
# register is base and written
return True
return False
return any(
op.reg == idautils.procregs.bp.reg and is_op_write(insn, op)
for op in get_insn_ops(insn, target_ops=(idaapi.o_reg,))
)
def is_frame_register(reg: int) -> bool:
@@ -326,10 +332,7 @@ def mask_op_val(op: idaapi.op_t) -> int:
def is_function_recursive(f: idaapi.func_t) -> bool:
"""check if function is recursive"""
for ref in idautils.CodeRefsTo(f.start_ea, True):
if f.contains(ref):
return True
return False
return any(f.contains(ref) for ref in idautils.CodeRefsTo(f.start_ea, True))
def is_basic_block_tight_loop(bb: idaapi.BasicBlock) -> bool:
@@ -378,8 +381,7 @@ def find_data_reference_from_insn(insn: idaapi.insn_t, max_depth: int = 10) -> i
def get_function_blocks(f: idaapi.func_t) -> Iterator[idaapi.BasicBlock]:
"""yield basic blocks contained in specified function"""
# leverage idaapi.FC_NOEXT flag to ignore useless external blocks referenced by the function
for block in idaapi.FlowChart(f, flags=(idaapi.FC_PREDS | idaapi.FC_NOEXT)):
yield block
yield from idaapi.FlowChart(f, flags=(idaapi.FC_PREDS | idaapi.FC_NOEXT))
def is_basic_block_return(bb: idaapi.BasicBlock) -> bool:

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# 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
@@ -73,7 +73,7 @@ def extract_insn_api_features(fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle)
"""
insn: idaapi.insn_t = ih.inner
if not insn.get_canon_mnem() in ("call", "jmp"):
if insn.get_canon_mnem() not in ("call", "jmp"):
return
# check calls to imported functions
@@ -172,7 +172,7 @@ def extract_insn_bytes_features(fh: FunctionHandle, bbh: BBHandle, ih: InsnHandl
if ref != insn.ea:
extracted_bytes = capa.features.extractors.ida.helpers.read_bytes_at(ref, MAX_BYTES_FEATURE_SIZE)
if extracted_bytes and not capa.features.extractors.helpers.all_zeros(extracted_bytes):
if not capa.features.extractors.ida.helpers.find_string_at(insn.ea):
if not capa.features.extractors.ida.helpers.find_string_at(ref):
# don't extract byte features for obvious strings
yield Bytes(extracted_bytes), ih.address
@@ -216,7 +216,7 @@ def extract_insn_offset_features(
p_info = capa.features.extractors.ida.helpers.get_op_phrase_info(op)
op_off = p_info.get("offset", None)
op_off = p_info.get("offset")
if op_off is None:
continue
@@ -398,14 +398,16 @@ def extract_insn_peb_access_characteristic_features(
if insn.itype not in (idaapi.NN_push, idaapi.NN_mov):
return
if all(map(lambda op: op.type != idaapi.o_mem, insn.ops)):
if all(op.type != idaapi.o_mem for op in insn.ops):
# try to optimize for only memory references
return
disasm = idc.GetDisasm(insn.ea)
if " fs:30h" in disasm or " gs:60h" in disasm:
# TODO: replace above with proper IDA
# TODO(mike-hunhoff): use proper IDA API for fetching segment access
# scanning the disassembly text is a hack.
# https://github.com/mandiant/capa/issues/1605
yield Characteristic("peb access"), ih.address
@@ -419,18 +421,22 @@ def extract_insn_segment_access_features(
"""
insn: idaapi.insn_t = ih.inner
if all(map(lambda op: op.type != idaapi.o_mem, insn.ops)):
if all(op.type != idaapi.o_mem for op in insn.ops):
# try to optimize for only memory references
return
disasm = idc.GetDisasm(insn.ea)
if " fs:" in disasm:
# TODO: replace above with proper IDA
# TODO(mike-hunhoff): use proper IDA API for fetching segment access
# scanning the disassembly text is a hack.
# https://github.com/mandiant/capa/issues/1605
yield Characteristic("fs access"), ih.address
if " gs:" in disasm:
# TODO: replace above with proper IDA
# TODO(mike-hunhoff): use proper IDA API for fetching segment access
# scanning the disassembly text is a hack.
# https://github.com/mandiant/capa/issues/1605
yield Characteristic("gs access"), ih.address
@@ -441,7 +447,7 @@ def extract_insn_cross_section_cflow(
insn: idaapi.insn_t = ih.inner
for ref in idautils.CodeRefsFrom(insn.ea, False):
if ref in get_imports(fh.ctx).keys():
if ref in get_imports(fh.ctx):
# ignore API calls
continue
if not idaapi.getseg(ref):
@@ -501,20 +507,3 @@ INSTRUCTION_HANDLERS = (
extract_function_calls_from,
extract_function_indirect_call_characteristic_features,
)
def main():
""" """
features = []
for f in capa.features.extractors.ida.helpers.get_functions(skip_thunks=True, skip_libs=True):
for bb in idaapi.FlowChart(f, flags=idaapi.FC_PREDS):
for insn in capa.features.extractors.ida.helpers.get_instructions_in_range(bb.start_ea, bb.end_ea):
features.extend(list(extract_features(f, bb, insn)))
import pprint
pprint.pprint(features)
if __name__ == "__main__":
main()

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# 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

View File

@@ -1,9 +1,28 @@
from typing import Dict, List, Tuple
# 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 Dict, List, Tuple, Union
from dataclasses import dataclass
from typing_extensions import TypeAlias
from capa.features.common import Feature
from capa.features.address import NO_ADDRESS, Address
from capa.features.extractors.base_extractor import BBHandle, InsnHandle, FunctionHandle, FeatureExtractor
from capa.features.address import NO_ADDRESS, Address, ThreadAddress, ProcessAddress, DynamicCallAddress
from capa.features.extractors.base_extractor import (
BBHandle,
CallHandle,
InsnHandle,
SampleHashes,
ThreadHandle,
ProcessHandle,
FunctionHandle,
StaticFeatureExtractor,
DynamicFeatureExtractor,
)
@dataclass
@@ -24,7 +43,7 @@ class FunctionFeatures:
@dataclass
class NullFeatureExtractor(FeatureExtractor):
class NullStaticFeatureExtractor(StaticFeatureExtractor):
"""
An extractor that extracts some user-provided features.
@@ -32,6 +51,7 @@ class NullFeatureExtractor(FeatureExtractor):
"""
base_address: Address
sample_hashes: SampleHashes
global_features: List[Feature]
file_features: List[Tuple[Address, Feature]]
functions: Dict[Address, FunctionFeatures]
@@ -39,6 +59,9 @@ class NullFeatureExtractor(FeatureExtractor):
def get_base_address(self):
return self.base_address
def get_sample_hashes(self) -> SampleHashes:
return self.sample_hashes
def extract_global_features(self):
for feature in self.global_features:
yield feature, NO_ADDRESS
@@ -70,3 +93,78 @@ class NullFeatureExtractor(FeatureExtractor):
def extract_insn_features(self, f, bb, insn):
for address, feature in self.functions[f.address].basic_blocks[bb.address].instructions[insn.address].features:
yield feature, address
@dataclass
class CallFeatures:
name: str
features: List[Tuple[Address, Feature]]
@dataclass
class ThreadFeatures:
features: List[Tuple[Address, Feature]]
calls: Dict[Address, CallFeatures]
@dataclass
class ProcessFeatures:
features: List[Tuple[Address, Feature]]
threads: Dict[Address, ThreadFeatures]
name: str
@dataclass
class NullDynamicFeatureExtractor(DynamicFeatureExtractor):
base_address: Address
sample_hashes: SampleHashes
global_features: List[Feature]
file_features: List[Tuple[Address, Feature]]
processes: Dict[Address, ProcessFeatures]
def extract_global_features(self):
for feature in self.global_features:
yield feature, NO_ADDRESS
def get_sample_hashes(self) -> SampleHashes:
return self.sample_hashes
def extract_file_features(self):
for address, feature in self.file_features:
yield feature, address
def get_processes(self):
for address in sorted(self.processes.keys()):
assert isinstance(address, ProcessAddress)
yield ProcessHandle(address=address, inner={})
def extract_process_features(self, ph):
for addr, feature in self.processes[ph.address].features:
yield feature, addr
def get_process_name(self, ph) -> str:
return self.processes[ph.address].name
def get_threads(self, ph):
for address in sorted(self.processes[ph.address].threads.keys()):
assert isinstance(address, ThreadAddress)
yield ThreadHandle(address=address, inner={})
def extract_thread_features(self, ph, th):
for addr, feature in self.processes[ph.address].threads[th.address].features:
yield feature, addr
def get_calls(self, ph, th):
for address in sorted(self.processes[ph.address].threads[th.address].calls.keys()):
assert isinstance(address, DynamicCallAddress)
yield CallHandle(address=address, inner={})
def extract_call_features(self, ph, th, ch):
for address, feature in self.processes[ph.address].threads[th.address].calls[ch.address].features:
yield feature, address
def get_call_name(self, ph, th, ch) -> str:
return self.processes[ph.address].threads[th.address].calls[ch.address].name
NullFeatureExtractor: TypeAlias = Union[NullStaticFeatureExtractor, NullDynamicFeatureExtractor]

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# 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
@@ -7,6 +7,7 @@
# See the License for the specific language governing permissions and limitations under the License.
import logging
from pathlib import Path
import pefile
@@ -18,7 +19,7 @@ import capa.features.extractors.strings
from capa.features.file import Export, Import, Section
from capa.features.common import OS, ARCH_I386, FORMAT_PE, ARCH_AMD64, OS_WINDOWS, Arch, Format, Characteristic
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__)
@@ -39,8 +40,20 @@ def extract_file_export_names(pe, **kwargs):
name = export.name.partition(b"\x00")[0].decode("ascii")
except UnicodeDecodeError:
continue
va = base_address + export.address
yield Export(name), AbsoluteVirtualAddress(va)
if export.forwarder is None:
va = base_address + export.address
yield Export(name), AbsoluteVirtualAddress(va)
else:
try:
forwarded_name = export.forwarder.partition(b"\x00")[0].decode("ascii")
except UnicodeDecodeError:
continue
forwarded_name = capa.features.extractors.helpers.reformat_forwarded_export_name(forwarded_name)
va = base_address + export.address
yield Export(forwarded_name), AbsoluteVirtualAddress(va)
yield Characteristic("forwarded export"), AbsoluteVirtualAddress(va)
def extract_file_import_names(pe, **kwargs):
@@ -64,14 +77,14 @@ def extract_file_import_names(pe, **kwargs):
for imp in dll.imports:
if imp.import_by_ordinal:
impname = "#%s" % imp.ordinal
impname = f"#{imp.ordinal}"
else:
try:
impname = imp.name.partition(b"\x00")[0].decode("ascii")
except UnicodeDecodeError:
continue
for name in capa.features.extractors.helpers.generate_symbols(modname, impname):
for name in capa.features.extractors.helpers.generate_symbols(modname, impname, include_dll=True):
yield Import(name), AbsoluteVirtualAddress(imp.address)
@@ -172,24 +185,22 @@ GLOBAL_HANDLERS = (
)
class PefileFeatureExtractor(FeatureExtractor):
def __init__(self, path: str):
super().__init__()
self.path = path
self.pe = pefile.PE(path)
class PefileFeatureExtractor(StaticFeatureExtractor):
def __init__(self, path: Path):
super().__init__(hashes=SampleHashes.from_bytes(path.read_bytes()))
self.path: Path = path
self.pe = pefile.PE(str(path))
def get_base_address(self):
return AbsoluteVirtualAddress(self.pe.OPTIONAL_HEADER.ImageBase)
def extract_global_features(self):
with open(self.path, "rb") as f:
buf = f.read()
buf = Path(self.path).read_bytes()
yield from extract_global_features(self.pe, buf)
def extract_file_features(self):
with open(self.path, "rb") as f:
buf = f.read()
buf = Path(self.path).read_bytes()
yield from extract_file_features(self.pe, buf)

View File

@@ -1,6 +1,6 @@
# strings code from FLOSS, https://github.com/mandiant/flare-floss
#
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# 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
@@ -9,6 +9,7 @@
# See the License for the specific language governing permissions and limitations under the License.
import re
import contextlib
from collections import namedtuple
ASCII_BYTE = r" !\"#\$%&\'\(\)\*\+,-\./0123456789:;<=>\?@ABCDEFGHIJKLMNOPQRSTUVWXYZ\[\]\^_`abcdefghijklmnopqrstuvwxyz\{\|\}\\\~\t".encode(
@@ -81,24 +82,5 @@ def extract_unicode_strings(buf, n=4):
reg = b"((?:[%s]\x00){%d,})" % (ASCII_BYTE, n)
r = re.compile(reg)
for match in r.finditer(buf):
try:
with contextlib.suppress(UnicodeDecodeError):
yield String(match.group().decode("utf-16"), match.start())
except UnicodeDecodeError:
pass
def main():
import sys
with open(sys.argv[1], "rb") as f:
b = f.read()
for s in extract_ascii_strings(b):
print("0x{:x}: {:s}".format(s.offset, s.s))
for s in extract_unicode_strings(b):
print("0x{:x}: {:s}".format(s.offset, s.s))
if __name__ == "__main__":
main()

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# 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
@@ -92,7 +92,6 @@ def is_mov_imm_to_stack(instr: envi.archs.i386.disasm.i386Opcode) -> bool:
if not src.isImmed():
return False
# TODO what about 64-bit operands?
if not isinstance(dst, envi.archs.i386.disasm.i386SibOper) and not isinstance(
dst, envi.archs.i386.disasm.i386RegMemOper
):
@@ -121,7 +120,7 @@ def get_printable_len(oper: envi.archs.i386.disasm.i386ImmOper) -> int:
elif oper.tsize == 8:
chars = struct.pack("<Q", oper.imm)
else:
raise ValueError("unexpected oper.tsize: %d" % (oper.tsize))
raise ValueError(f"unexpected oper.tsize: {oper.tsize}")
if is_printable_ascii(chars):
return oper.tsize
@@ -141,7 +140,7 @@ def is_printable_ascii(chars: bytes) -> bool:
def is_printable_utf16le(chars: bytes) -> bool:
if all(c == b"\x00" for c in chars[1::2]):
if all(c == 0x0 for c in chars[1::2]):
return is_printable_ascii(chars[::2])
return False

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# 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
@@ -6,7 +6,8 @@
# 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 List, Tuple, Iterator
from typing import Any, Dict, List, Tuple, Iterator
from pathlib import Path
import viv_utils
import viv_utils.flirt
@@ -19,23 +20,28 @@ import capa.features.extractors.viv.function
import capa.features.extractors.viv.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,
)
logger = logging.getLogger(__name__)
class VivisectFeatureExtractor(FeatureExtractor):
def __init__(self, vw, path):
super().__init__()
class VivisectFeatureExtractor(StaticFeatureExtractor):
def __init__(self, vw, path: Path, os):
self.vw = vw
self.path = path
with open(self.path, "rb") as f:
self.buf = f.read()
self.buf = path.read_bytes()
super().__init__(hashes=SampleHashes.from_bytes(self.buf))
# pre-compute these because we'll yield them at *every* scope.
self.global_features: List[Tuple[Feature, Address]] = []
self.global_features.extend(capa.features.extractors.viv.file.extract_file_format(self.buf))
self.global_features.extend(capa.features.extractors.common.extract_os(self.buf))
self.global_features.extend(capa.features.extractors.common.extract_os(self.buf, os))
self.global_features.extend(capa.features.extractors.viv.global_.extract_arch(self.vw))
def get_base_address(self):
@@ -49,8 +55,11 @@ class VivisectFeatureExtractor(FeatureExtractor):
yield from capa.features.extractors.viv.file.extract_features(self.vw, self.buf)
def get_functions(self) -> Iterator[FunctionHandle]:
cache: Dict[str, Any] = {}
for va in sorted(self.vw.getFunctions()):
yield FunctionHandle(address=AbsoluteVirtualAddress(va), inner=viv_utils.Function(self.vw, va))
yield FunctionHandle(
address=AbsoluteVirtualAddress(va), inner=viv_utils.Function(self.vw, va), ctx={"cache": cache}
)
def extract_function_features(self, fh: FunctionHandle) -> Iterator[Tuple[Feature, Address]]:
yield from capa.features.extractors.viv.function.extract_features(fh)

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# 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
@@ -8,6 +8,7 @@
from typing import Tuple, Iterator
import PE.carve as pe_carve # vivisect PE
import vivisect
import viv_utils
import viv_utils.flirt
@@ -16,7 +17,7 @@ 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 String, Feature, Characteristic
from capa.features.common import Feature, Characteristic
from capa.features.address import Address, FileOffsetAddress, AbsoluteVirtualAddress
@@ -25,10 +26,35 @@ def extract_file_embedded_pe(buf, **kwargs) -> Iterator[Tuple[Feature, Address]]
yield Characteristic("embedded pe"), FileOffsetAddress(offset)
def extract_file_export_names(vw, **kwargs) -> Iterator[Tuple[Feature, Address]]:
def get_first_vw_filename(vw: vivisect.VivWorkspace):
# vivisect associates metadata with each file that its loaded into the workspace.
# capa only loads a single file into each workspace.
# so to access the metadata for the file in question, we can just take the first one.
# otherwise, we'd have to pass around the module name of the file we're analyzing,
# which is a pain.
#
# so this is a simplifying assumption.
return next(iter(vw.filemeta.keys()))
def extract_file_export_names(vw: vivisect.VivWorkspace, **kwargs) -> Iterator[Tuple[Feature, Address]]:
for va, _, name, _ in vw.getExports():
yield Export(name), AbsoluteVirtualAddress(va)
if vw.getMeta("Format") == "pe":
pe = vw.parsedbin
baseaddr = pe.IMAGE_NT_HEADERS.OptionalHeader.ImageBase
for rva, _, forwarded_name in vw.getFileMeta(get_first_vw_filename(vw), "forwarders"):
try:
forwarded_name = forwarded_name.partition(b"\x00")[0].decode("ascii")
except UnicodeDecodeError:
continue
forwarded_name = capa.features.extractors.helpers.reformat_forwarded_export_name(forwarded_name)
va = baseaddr + rva
yield Export(forwarded_name), AbsoluteVirtualAddress(va)
yield Characteristic("forwarded export"), AbsoluteVirtualAddress(va)
def extract_file_import_names(vw, **kwargs) -> Iterator[Tuple[Feature, Address]]:
"""
@@ -44,10 +70,10 @@ def extract_file_import_names(vw, **kwargs) -> Iterator[Tuple[Feature, Address]]
modname, impname = tinfo.split(".", 1)
if is_viv_ord_impname(impname):
# replace ord prefix with #
impname = "#%s" % impname[len("ord") :]
impname = "#" + impname[len("ord") :]
addr = AbsoluteVirtualAddress(va)
for name in capa.features.extractors.helpers.generate_symbols(modname, impname):
for name in capa.features.extractors.helpers.generate_symbols(modname, impname, include_dll=True):
yield Import(name), addr

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# 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
@@ -11,9 +11,11 @@ import envi
import viv_utils
import vivisect.const
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.elf import SymTab
from capa.features.extractors.base_extractor import FunctionHandle
@@ -30,6 +32,28 @@ def interface_extract_function_XXX(fh: FunctionHandle) -> Iterator[Tuple[Feature
raise NotImplementedError
def extract_function_symtab_names(fh: FunctionHandle) -> Iterator[Tuple[Feature, Address]]:
if fh.inner.vw.metadata["Format"] == "elf":
# the file's symbol table gets added to the metadata of the vivisect workspace.
# this is in order to eliminate the computational overhead of refetching symtab each time.
if "symtab" not in fh.ctx["cache"]:
try:
fh.ctx["cache"]["symtab"] = SymTab.from_viv(fh.inner.vw.parsedbin)
except Exception:
fh.ctx["cache"]["symtab"] = None
symtab = fh.ctx["cache"]["symtab"]
if symtab:
for symbol in symtab.get_symbols():
sym_name = symtab.get_name(symbol)
sym_value = symbol.value
sym_info = symbol.info
STT_FUNC = 0x2
if sym_value == fh.address and sym_info & STT_FUNC != 0:
yield FunctionName(sym_name), fh.address
def extract_function_calls_to(fhandle: FunctionHandle) -> Iterator[Tuple[Feature, Address]]:
f: viv_utils.Function = fhandle.inner
for src, _, _, _ in f.vw.getXrefsTo(f.va, rtype=vivisect.const.REF_CODE):
@@ -47,6 +71,10 @@ def extract_function_loop(fhandle: FunctionHandle) -> Iterator[Tuple[Feature, Ad
for bb in f.basic_blocks:
if len(bb.instructions) > 0:
for bva, bflags in bb.instructions[-1].getBranches():
if bva is None:
# vivisect may be unable to recover the call target, e.g. on dynamic calls like `call esi`
# for this bva is None, and we don't want to add it for loop detection, ref: vivisect#574
continue
# vivisect does not set branch flags for non-conditional jmp so add explicit check
if (
bflags & envi.BR_COND
@@ -75,4 +103,8 @@ def extract_features(fh: FunctionHandle) -> Iterator[Tuple[Feature, Address]]:
yield feature, addr
FUNCTION_HANDLERS = (extract_function_calls_to, extract_function_loop)
FUNCTION_HANDLERS = (
extract_function_symtab_names,
extract_function_calls_to,
extract_function_loop,
)

View File

@@ -1,9 +1,13 @@
# 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
import envi.archs.i386
import envi.archs.amd64
from capa.features.common import ARCH_I386, ARCH_AMD64, Arch, Feature
from capa.features.address import NO_ADDRESS, Address
@@ -11,10 +15,11 @@ logger = logging.getLogger(__name__)
def extract_arch(vw) -> Iterator[Tuple[Feature, Address]]:
if isinstance(vw.arch, envi.archs.amd64.Amd64Module):
arch = vw.getMeta("Architecture")
if arch == "amd64":
yield Arch(ARCH_AMD64), NO_ADDRESS
elif isinstance(vw.arch, envi.archs.i386.i386Module):
elif arch == "i386":
yield Arch(ARCH_I386), NO_ADDRESS
else:

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# 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

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# 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
@@ -7,7 +7,7 @@
# See the License for the specific language governing permissions and limitations under the License.
import collections
from typing import Set, List, Deque, Tuple, Union, Optional
from typing import Set, List, Deque, Tuple, Optional
import envi
import vivisect.const
@@ -71,7 +71,7 @@ class NotFoundError(Exception):
pass
def find_definition(vw: VivWorkspace, va: int, reg: int) -> Tuple[int, Union[int, None]]:
def find_definition(vw: VivWorkspace, va: int, reg: int) -> Tuple[int, Optional[int]]:
"""
scan backwards from the given address looking for assignments to the given register.
if a constant, return that value.
@@ -87,8 +87,8 @@ def find_definition(vw: VivWorkspace, va: int, reg: int) -> Tuple[int, Union[int
raises:
NotFoundError: when the definition cannot be found.
"""
q = collections.deque() # type: Deque[int]
seen = set([]) # type: Set[int]
q: Deque[int] = collections.deque()
seen: Set[int] = set()
q.extend(get_previous_instructions(vw, va))
while q:

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# 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
@@ -22,6 +22,7 @@ import capa.features.extractors.viv.helpers
from capa.features.insn import API, MAX_STRUCTURE_SIZE, Number, Offset, Mnemonic, OperandNumber, OperandOffset
from capa.features.common import MAX_BYTES_FEATURE_SIZE, THUNK_CHAIN_DEPTH_DELTA, Bytes, String, Feature, Characteristic
from capa.features.address import Address, AbsoluteVirtualAddress
from capa.features.extractors.elf import SymTab
from capa.features.extractors.base_extractor import BBHandle, InsnHandle, FunctionHandle
from capa.features.extractors.viv.indirect_calls import NotFoundError, resolve_indirect_call
@@ -109,6 +110,26 @@ def extract_insn_api_features(fh: FunctionHandle, bb, ih: InsnHandle) -> Iterato
if not target:
return
if f.vw.metadata["Format"] == "elf":
if "symtab" not in fh.ctx["cache"]:
# the symbol table gets stored as a function's attribute in order to avoid running
# this code everytime the call is made, thus preventing the computational overhead.
try:
fh.ctx["cache"]["symtab"] = SymTab.from_viv(f.vw.parsedbin)
except Exception:
fh.ctx["cache"]["symtab"] = None
symtab = fh.ctx["cache"]["symtab"]
if symtab:
for symbol in symtab.get_symbols():
sym_name = symtab.get_name(symbol)
sym_value = symbol.value
sym_info = symbol.info
STT_FUNC = 0x2
if sym_value == target and sym_info & STT_FUNC != 0:
yield API(sym_name), ih.address
if viv_utils.flirt.is_library_function(f.vw, target):
name = viv_utils.get_function_name(f.vw, target)
yield API(name), ih.address
@@ -175,8 +196,13 @@ def derefs(vw, p):
while True:
if not vw.isValidPointer(p):
return
yield p
if vw.isProbablyString(p) or vw.isProbablyUnicode(p):
# don't deref strings that coincidentally are pointers
return
try:
next = vw.readMemoryPtr(p)
except Exception:
@@ -262,16 +288,16 @@ def extract_insn_bytes_features(fh: FunctionHandle, bb, ih: InsnHandle) -> Itera
else:
continue
for v in derefs(f.vw, v):
for vv in derefs(f.vw, v):
try:
buf = read_bytes(f.vw, v)
buf = read_bytes(f.vw, vv)
except envi.exc.SegmentationViolation:
continue
if capa.features.extractors.helpers.all_zeros(buf):
continue
if f.vw.isProbablyString(v):
if f.vw.isProbablyString(vv) or f.vw.isProbablyUnicode(vv):
# don't extract byte features for obvious strings
continue
@@ -325,7 +351,6 @@ def is_security_cookie(f, bb, insn) -> bool:
if oper.isReg() and oper.reg not in [
envi.archs.i386.regs.REG_ESP,
envi.archs.i386.regs.REG_EBP,
# TODO: do x64 support for real.
envi.archs.amd64.regs.REG_RBP,
envi.archs.amd64.regs.REG_RSP,
]:
@@ -385,9 +410,7 @@ def extract_insn_obfs_call_plus_5_characteristic_features(f, bb, ih: InsnHandle)
if insn.va + 5 == insn.opers[0].getOperValue(insn):
yield Characteristic("call $+5"), ih.address
if isinstance(insn.opers[0], envi.archs.i386.disasm.i386ImmMemOper) or isinstance(
insn.opers[0], envi.archs.amd64.disasm.Amd64RipRelOper
):
if isinstance(insn.opers[0], (envi.archs.i386.disasm.i386ImmMemOper, envi.archs.amd64.disasm.Amd64RipRelOper)):
if insn.va + 5 == insn.opers[0].getOperAddr(insn):
yield Characteristic("call $+5"), ih.address
@@ -396,7 +419,6 @@ def extract_insn_peb_access_characteristic_features(f, bb, ih: InsnHandle) -> It
"""
parse peb access from the given function. fs:[0x30] on x86, gs:[0x60] on x64
"""
# TODO handle where fs/gs are loaded into a register or onto the stack and used later
insn: envi.Opcode = ih.inner
if insn.mnem not in ["push", "mov"]:
@@ -620,7 +642,6 @@ def extract_op_offset_features(
if oper.reg == envi.archs.i386.regs.REG_EBP:
return
# TODO: do x64 support for real.
if oper.reg == envi.archs.amd64.regs.REG_RBP:
return
@@ -674,9 +695,9 @@ def extract_op_string_features(
else:
return
for v in derefs(f.vw, v):
for vv in derefs(f.vw, v):
try:
s = read_string(f.vw, v).rstrip("\x00")
s = read_string(f.vw, vv).rstrip("\x00")
except ValueError:
continue
else:

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# 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

View File

@@ -1,7 +1,7 @@
"""
capa freeze file format: `| capa0000 | + zlib(utf-8(json(...)))`
Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
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
@@ -9,12 +9,17 @@ Unless required by applicable law or agreed to in writing, software distributed
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 json
import zlib
import logging
from enum import Enum
from typing import Any, List, Tuple, Union
from typing import List, Tuple, Union, Literal
from pydantic import Field, BaseModel
from pydantic import Field, BaseModel, ConfigDict
# TODO(williballenthin): use typing.TypeAlias directly in Python 3.10+
# https://github.com/mandiant/capa/issues/1699
from typing_extensions import TypeAlias
import capa.helpers
import capa.version
@@ -23,16 +28,23 @@ import capa.features.insn
import capa.features.common
import capa.features.address
import capa.features.basicblock
import capa.features.extractors.base_extractor
import capa.features.extractors.null as null
from capa.helpers import assert_never
from capa.features.freeze.features import Feature, feature_from_capa
from capa.features.extractors.base_extractor import (
SampleHashes,
FeatureExtractor,
StaticFeatureExtractor,
DynamicFeatureExtractor,
)
logger = logging.getLogger(__name__)
CURRENT_VERSION = 3
class HashableModel(BaseModel):
class Config:
frozen = True
model_config = ConfigDict(frozen=True)
class AddressType(str, Enum):
@@ -41,12 +53,15 @@ class AddressType(str, Enum):
FILE = "file"
DN_TOKEN = "dn token"
DN_TOKEN_OFFSET = "dn token offset"
PROCESS = "process"
THREAD = "thread"
CALL = "call"
NO_ADDRESS = "no address"
class Address(HashableModel):
type: AddressType
value: Union[int, Tuple[int, int], None]
value: Union[int, Tuple[int, ...], None] = None # None default value to support deserialization of NO_ADDRESS
@classmethod
def from_capa(cls, a: capa.features.address.Address) -> "Address":
@@ -65,6 +80,15 @@ class Address(HashableModel):
elif isinstance(a, capa.features.address.DNTokenOffsetAddress):
return cls(type=AddressType.DN_TOKEN_OFFSET, value=(a.token, a.offset))
elif isinstance(a, capa.features.address.ProcessAddress):
return cls(type=AddressType.PROCESS, value=(a.ppid, a.pid))
elif isinstance(a, capa.features.address.ThreadAddress):
return cls(type=AddressType.THREAD, value=(a.process.ppid, a.process.pid, a.tid))
elif isinstance(a, capa.features.address.DynamicCallAddress):
return cls(type=AddressType.CALL, value=(a.thread.process.ppid, a.thread.process.pid, a.thread.tid, a.id))
elif a == capa.features.address.NO_ADDRESS or isinstance(a, capa.features.address._NoAddress):
return cls(type=AddressType.NO_ADDRESS, value=None)
@@ -101,6 +125,33 @@ class Address(HashableModel):
assert isinstance(offset, int)
return capa.features.address.DNTokenOffsetAddress(token, offset)
elif self.type is AddressType.PROCESS:
assert isinstance(self.value, tuple)
ppid, pid = self.value
assert isinstance(ppid, int)
assert isinstance(pid, int)
return capa.features.address.ProcessAddress(ppid=ppid, pid=pid)
elif self.type is AddressType.THREAD:
assert isinstance(self.value, tuple)
ppid, pid, tid = self.value
assert isinstance(ppid, int)
assert isinstance(pid, int)
assert isinstance(tid, int)
return capa.features.address.ThreadAddress(
process=capa.features.address.ProcessAddress(ppid=ppid, pid=pid), tid=tid
)
elif self.type is AddressType.CALL:
assert isinstance(self.value, tuple)
ppid, pid, tid, id_ = self.value
return capa.features.address.DynamicCallAddress(
thread=capa.features.address.ThreadAddress(
process=capa.features.address.ProcessAddress(ppid=ppid, pid=pid), tid=tid
),
id=id_,
)
elif self.type is AddressType.NO_ADDRESS:
return capa.features.address.NO_ADDRESS
@@ -131,6 +182,48 @@ class FileFeature(HashableModel):
feature: Feature
class ProcessFeature(HashableModel):
"""
args:
process: the address of the process to which this feature belongs.
address: the address at which this feature is found.
process != address because, e.g., the feature may be found *within* the scope (process).
"""
process: Address
address: Address
feature: Feature
class ThreadFeature(HashableModel):
"""
args:
thread: the address of the thread to which this feature belongs.
address: the address at which this feature is found.
thread != address because, e.g., the feature may be found *within* the scope (thread).
"""
thread: Address
address: Address
feature: Feature
class CallFeature(HashableModel):
"""
args:
call: the address of the call to which this feature belongs.
address: the address at which this feature is found.
call != address for consistency with Process and Thread.
"""
call: Address
address: Address
feature: Feature
class FunctionFeature(HashableModel):
"""
args:
@@ -159,9 +252,7 @@ class BasicBlockFeature(HashableModel):
basic_block: Address = Field(alias="basic block")
address: Address
feature: Feature
class Config:
allow_population_by_field_name = True
model_config = ConfigDict(populate_by_name=True)
class InstructionFeature(HashableModel):
@@ -170,8 +261,7 @@ class InstructionFeature(HashableModel):
instruction: the address of the instruction to which this feature belongs.
address: the address at which this feature is found.
instruction != address because, e.g., the feature may be found *within* the scope (basic block),
versus right at its starting address.
instruction != address because, for consistency with Function and BasicBlock.
"""
instruction: Address
@@ -194,43 +284,65 @@ class FunctionFeatures(BaseModel):
address: Address
features: Tuple[FunctionFeature, ...]
basic_blocks: Tuple[BasicBlockFeatures, ...] = Field(alias="basic blocks")
class Config:
allow_population_by_field_name = True
model_config = ConfigDict(populate_by_name=True)
class Features(BaseModel):
class CallFeatures(BaseModel):
address: Address
name: str
features: Tuple[CallFeature, ...]
class ThreadFeatures(BaseModel):
address: Address
features: Tuple[ThreadFeature, ...]
calls: Tuple[CallFeatures, ...]
class ProcessFeatures(BaseModel):
address: Address
name: str
features: Tuple[ProcessFeature, ...]
threads: Tuple[ThreadFeatures, ...]
class StaticFeatures(BaseModel):
global_: Tuple[GlobalFeature, ...] = Field(alias="global")
file: Tuple[FileFeature, ...]
functions: Tuple[FunctionFeatures, ...]
model_config = ConfigDict(populate_by_name=True)
class Config:
allow_population_by_field_name = True
class DynamicFeatures(BaseModel):
global_: Tuple[GlobalFeature, ...] = Field(alias="global")
file: Tuple[FileFeature, ...]
processes: Tuple[ProcessFeatures, ...]
model_config = ConfigDict(populate_by_name=True)
Features: TypeAlias = Union[StaticFeatures, DynamicFeatures]
class Extractor(BaseModel):
name: str
version: str = capa.version.__version__
class Config:
allow_population_by_field_name = True
model_config = ConfigDict(populate_by_name=True)
class Freeze(BaseModel):
version: int = 2
version: int = CURRENT_VERSION
base_address: Address = Field(alias="base address")
sample_hashes: SampleHashes
flavor: Literal["static", "dynamic"]
extractor: Extractor
features: Features
class Config:
allow_population_by_field_name = True
model_config = ConfigDict(populate_by_name=True)
def dumps(extractor: capa.features.extractors.base_extractor.FeatureExtractor) -> str:
def dumps_static(extractor: StaticFeatureExtractor) -> str:
"""
serialize the given extractor to a string
"""
global_features: List[GlobalFeature] = []
for feature, _ in extractor.extract_global_features():
global_features.append(
@@ -268,7 +380,8 @@ def dumps(extractor: capa.features.extractors.base_extractor.FeatureExtractor) -
basic_block=bbaddr,
address=Address.from_capa(addr),
feature=feature_from_capa(feature),
)
) # type: ignore
# Mypy is unable to recognise `basic_block` as a argument due to alias
for feature, addr in extractor.extract_basic_block_features(f, bb)
]
@@ -287,52 +400,168 @@ def dumps(extractor: capa.features.extractors.base_extractor.FeatureExtractor) -
instructions.append(
InstructionFeatures(
address=iaddr,
features=ifeatures,
features=tuple(ifeatures),
)
)
basic_blocks.append(
BasicBlockFeatures(
address=bbaddr,
features=bbfeatures,
instructions=instructions,
features=tuple(bbfeatures),
instructions=tuple(instructions),
)
)
function_features.append(
FunctionFeatures(
address=faddr,
features=ffeatures,
features=tuple(ffeatures),
basic_blocks=basic_blocks,
) # type: ignore
# Mypy is unable to recognise `basic_blocks` as a argument due to alias
)
features = StaticFeatures(
global_=global_features,
file=tuple(file_features),
functions=tuple(function_features),
) # type: ignore
# Mypy is unable to recognise `global_` as a argument due to alias
freeze = Freeze(
version=CURRENT_VERSION,
base_address=Address.from_capa(extractor.get_base_address()),
sample_hashes=extractor.get_sample_hashes(),
flavor="static",
extractor=Extractor(name=extractor.__class__.__name__),
features=features,
) # type: ignore
# Mypy is unable to recognise `base_address` as a argument due to alias
return freeze.model_dump_json()
def dumps_dynamic(extractor: DynamicFeatureExtractor) -> str:
"""
serialize the given extractor to a string
"""
global_features: List[GlobalFeature] = []
for feature, _ in extractor.extract_global_features():
global_features.append(
GlobalFeature(
feature=feature_from_capa(feature),
)
)
features = Features(
file_features: List[FileFeature] = []
for feature, address in extractor.extract_file_features():
file_features.append(
FileFeature(
feature=feature_from_capa(feature),
address=Address.from_capa(address),
)
)
process_features: List[ProcessFeatures] = []
for p in extractor.get_processes():
paddr = Address.from_capa(p.address)
pname = extractor.get_process_name(p)
pfeatures = [
ProcessFeature(
process=paddr,
address=Address.from_capa(addr),
feature=feature_from_capa(feature),
)
for feature, addr in extractor.extract_process_features(p)
]
threads = []
for t in extractor.get_threads(p):
taddr = Address.from_capa(t.address)
tfeatures = [
ThreadFeature(
basic_block=taddr,
address=Address.from_capa(addr),
feature=feature_from_capa(feature),
) # type: ignore
# Mypy is unable to recognise `basic_block` as a argument due to alias
for feature, addr in extractor.extract_thread_features(p, t)
]
calls = []
for call in extractor.get_calls(p, t):
caddr = Address.from_capa(call.address)
cname = extractor.get_call_name(p, t, call)
cfeatures = [
CallFeature(
call=caddr,
address=Address.from_capa(addr),
feature=feature_from_capa(feature),
)
for feature, addr in extractor.extract_call_features(p, t, call)
]
calls.append(
CallFeatures(
address=caddr,
name=cname,
features=tuple(cfeatures),
)
)
threads.append(
ThreadFeatures(
address=taddr,
features=tuple(tfeatures),
calls=tuple(calls),
)
)
process_features.append(
ProcessFeatures(
address=paddr,
name=pname,
features=tuple(pfeatures),
threads=tuple(threads),
)
)
features = DynamicFeatures(
global_=global_features,
file=file_features,
functions=function_features,
)
file=tuple(file_features),
processes=tuple(process_features),
) # type: ignore
# Mypy is unable to recognise `global_` as a argument due to alias
# workaround around mypy issue: https://github.com/python/mypy/issues/1424
get_base_addr = getattr(extractor, "get_base_addr", None)
base_addr = get_base_addr() if get_base_addr else capa.features.address.NO_ADDRESS
freeze = Freeze(
version=2,
base_address=Address.from_capa(extractor.get_base_address()),
version=CURRENT_VERSION,
base_address=Address.from_capa(base_addr),
sample_hashes=extractor.get_sample_hashes(),
flavor="dynamic",
extractor=Extractor(name=extractor.__class__.__name__),
features=features,
)
) # type: ignore
# Mypy is unable to recognise `base_address` as a argument due to alias
return freeze.json()
return freeze.model_dump_json()
def loads(s: str) -> capa.features.extractors.base_extractor.FeatureExtractor:
"""deserialize a set of features (as a NullFeatureExtractor) from a string."""
import capa.features.extractors.null as null
def loads_static(s: str) -> StaticFeatureExtractor:
"""deserialize a set of features (as a NullStaticFeatureExtractor) from a string."""
freeze = Freeze.model_validate_json(s)
if freeze.version != CURRENT_VERSION:
raise ValueError(f"unsupported freeze format version: {freeze.version}")
freeze = Freeze.parse_raw(s)
if freeze.version != 2:
raise ValueError("unsupported freeze format version: %d", freeze.version)
assert freeze.flavor == "static"
assert isinstance(freeze.features, StaticFeatures)
return null.NullFeatureExtractor(
return null.NullStaticFeatureExtractor(
base_address=freeze.base_address.to_capa(),
sample_hashes=freeze.sample_hashes,
global_features=[f.feature.to_capa() for f in freeze.features.global_],
file_features=[(f.address.to_capa(), f.feature.to_capa()) for f in freeze.features.file],
functions={
@@ -356,10 +585,59 @@ def loads(s: str) -> capa.features.extractors.base_extractor.FeatureExtractor:
)
def loads_dynamic(s: str) -> DynamicFeatureExtractor:
"""deserialize a set of features (as a NullDynamicFeatureExtractor) from a string."""
freeze = Freeze.model_validate_json(s)
if freeze.version != CURRENT_VERSION:
raise ValueError(f"unsupported freeze format version: {freeze.version}")
assert freeze.flavor == "dynamic"
assert isinstance(freeze.features, DynamicFeatures)
return null.NullDynamicFeatureExtractor(
base_address=freeze.base_address.to_capa(),
sample_hashes=freeze.sample_hashes,
global_features=[f.feature.to_capa() for f in freeze.features.global_],
file_features=[(f.address.to_capa(), f.feature.to_capa()) for f in freeze.features.file],
processes={
p.address.to_capa(): null.ProcessFeatures(
name=p.name,
features=[(fe.address.to_capa(), fe.feature.to_capa()) for fe in p.features],
threads={
t.address.to_capa(): null.ThreadFeatures(
features=[(fe.address.to_capa(), fe.feature.to_capa()) for fe in t.features],
calls={
c.address.to_capa(): null.CallFeatures(
name=c.name,
features=[(fe.address.to_capa(), fe.feature.to_capa()) for fe in c.features],
)
for c in t.calls
},
)
for t in p.threads
},
)
for p in freeze.features.processes
},
)
MAGIC = "capa0000".encode("ascii")
def dump(extractor: capa.features.extractors.base_extractor.FeatureExtractor) -> bytes:
def dumps(extractor: FeatureExtractor) -> str:
"""serialize the given extractor to a string."""
if isinstance(extractor, StaticFeatureExtractor):
doc = dumps_static(extractor)
elif isinstance(extractor, DynamicFeatureExtractor):
doc = dumps_dynamic(extractor)
else:
raise ValueError("Invalid feature extractor")
return doc
def dump(extractor: FeatureExtractor) -> bytes:
"""serialize the given extractor to a byte array."""
return MAGIC + zlib.compress(dumps(extractor).encode("utf-8"))
@@ -368,16 +646,34 @@ def is_freeze(buf: bytes) -> bool:
return buf[: len(MAGIC)] == MAGIC
def load(buf: bytes) -> capa.features.extractors.base_extractor.FeatureExtractor:
def loads(s: str):
doc = json.loads(s)
if doc["version"] != CURRENT_VERSION:
raise ValueError(f"unsupported freeze format version: {doc['version']}")
if doc["flavor"] == "static":
return loads_static(s)
elif doc["flavor"] == "dynamic":
return loads_dynamic(s)
else:
raise ValueError(f"unsupported freeze format flavor: {doc['flavor']}")
def load(buf: bytes):
"""deserialize a set of features (as a NullFeatureExtractor) from a byte array."""
if not is_freeze(buf):
raise ValueError("missing magic header")
return loads(zlib.decompress(buf[len(MAGIC) :]).decode("utf-8"))
s = zlib.decompress(buf[len(MAGIC) :]).decode("utf-8")
return loads(s)
def main(argv=None):
import sys
import argparse
from pathlib import Path
import capa.main
@@ -385,17 +681,16 @@ def main(argv=None):
argv = sys.argv[1:]
parser = argparse.ArgumentParser(description="save capa features to a file")
capa.main.install_common_args(parser, {"sample", "format", "backend", "signatures"})
capa.main.install_common_args(parser, {"sample", "format", "backend", "os", "signatures"})
parser.add_argument("output", type=str, help="Path to output file")
args = parser.parse_args(args=argv)
capa.main.handle_common_args(args)
sigpaths = capa.main.get_signatures(args.signatures)
extractor = capa.main.get_extractor(args.sample, args.format, args.backend, sigpaths, False)
extractor = capa.main.get_extractor(args.sample, args.format, args.os, args.backend, sigpaths, False)
with open(args.output, "wb") as f:
f.write(dump(extractor))
Path(args.output).write_bytes(dump(extractor))
return 0

View File

@@ -1,7 +1,14 @@
# 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 Union, Optional
from pydantic import Field, BaseModel
from pydantic import Field, BaseModel, ConfigDict
import capa.features.file
import capa.features.insn
@@ -10,9 +17,7 @@ import capa.features.basicblock
class FeatureModel(BaseModel):
class Config:
frozen = True
allow_population_by_field_name = True
model_config = ConfigDict(frozen=True, populate_by_name=True)
def to_capa(self) -> capa.features.common.Feature:
if isinstance(self, OSFeature):
@@ -101,59 +106,79 @@ class FeatureModel(BaseModel):
def feature_from_capa(f: capa.features.common.Feature) -> "Feature":
if isinstance(f, capa.features.common.OS):
assert isinstance(f.value, str)
return OSFeature(os=f.value, description=f.description)
elif isinstance(f, capa.features.common.Arch):
assert isinstance(f.value, str)
return ArchFeature(arch=f.value, description=f.description)
elif isinstance(f, capa.features.common.Format):
assert isinstance(f.value, str)
return FormatFeature(format=f.value, description=f.description)
elif isinstance(f, capa.features.common.MatchedRule):
assert isinstance(f.value, str)
return MatchFeature(match=f.value, description=f.description)
elif isinstance(f, capa.features.common.Characteristic):
assert isinstance(f.value, str)
return CharacteristicFeature(characteristic=f.value, description=f.description)
elif isinstance(f, capa.features.file.Export):
assert isinstance(f.value, str)
return ExportFeature(export=f.value, description=f.description)
elif isinstance(f, capa.features.file.Import):
return ImportFeature(import_=f.value, description=f.description)
assert isinstance(f.value, str)
return ImportFeature(import_=f.value, description=f.description) # type: ignore
# Mypy is unable to recognise `import_` as a argument due to alias
elif isinstance(f, capa.features.file.Section):
assert isinstance(f.value, str)
return SectionFeature(section=f.value, description=f.description)
elif isinstance(f, capa.features.file.FunctionName):
return FunctionNameFeature(function_name=f.value, description=f.description)
assert isinstance(f.value, str)
return FunctionNameFeature(function_name=f.value, description=f.description) # type: ignore
# Mypy is unable to recognise `function_name` as a argument due to alias
# must come before check for String due to inheritance
elif isinstance(f, capa.features.common.Substring):
assert isinstance(f.value, str)
return SubstringFeature(substring=f.value, description=f.description)
# must come before check for String due to inheritance
elif isinstance(f, capa.features.common.Regex):
assert isinstance(f.value, str)
return RegexFeature(regex=f.value, description=f.description)
elif isinstance(f, capa.features.common.String):
assert isinstance(f.value, str)
return StringFeature(string=f.value, description=f.description)
elif isinstance(f, capa.features.common.Class):
return ClassFeature(class_=f.value, description=f.description)
assert isinstance(f.value, str)
return ClassFeature(class_=f.value, description=f.description) # type: ignore
# Mypy is unable to recognise `class_` as a argument due to alias
elif isinstance(f, capa.features.common.Namespace):
assert isinstance(f.value, str)
return NamespaceFeature(namespace=f.value, description=f.description)
elif isinstance(f, capa.features.basicblock.BasicBlock):
return BasicBlockFeature(description=f.description)
elif isinstance(f, capa.features.insn.API):
assert isinstance(f.value, str)
return APIFeature(api=f.value, description=f.description)
elif isinstance(f, capa.features.insn.Property):
assert isinstance(f.value, str)
return PropertyFeature(property=f.value, access=f.access, description=f.description)
elif isinstance(f, capa.features.insn.Number):
assert isinstance(f.value, (int, float))
return NumberFeature(number=f.value, description=f.description)
elif isinstance(f, capa.features.common.Bytes):
@@ -162,16 +187,22 @@ def feature_from_capa(f: capa.features.common.Feature) -> "Feature":
return BytesFeature(bytes=binascii.hexlify(buf).decode("ascii"), description=f.description)
elif isinstance(f, capa.features.insn.Offset):
assert isinstance(f.value, int)
return OffsetFeature(offset=f.value, description=f.description)
elif isinstance(f, capa.features.insn.Mnemonic):
assert isinstance(f.value, str)
return MnemonicFeature(mnemonic=f.value, description=f.description)
elif isinstance(f, capa.features.insn.OperandNumber):
return OperandNumberFeature(index=f.index, operand_number=f.value, description=f.description)
assert isinstance(f.value, int)
return OperandNumberFeature(index=f.index, operand_number=f.value, description=f.description) # type: ignore
# Mypy is unable to recognise `operand_number` as a argument due to alias
elif isinstance(f, capa.features.insn.OperandOffset):
return OperandOffsetFeature(index=f.index, operand_offset=f.value, description=f.description)
assert isinstance(f.value, int)
return OperandOffsetFeature(index=f.index, operand_offset=f.value, description=f.description) # type: ignore
# Mypy is unable to recognise `operand_offset` as a argument due to alias
else:
raise NotImplementedError(f"feature_from_capa({type(f)}) not implemented")
@@ -180,141 +211,141 @@ def feature_from_capa(f: capa.features.common.Feature) -> "Feature":
class OSFeature(FeatureModel):
type: str = "os"
os: str
description: Optional[str]
description: Optional[str] = None
class ArchFeature(FeatureModel):
type: str = "arch"
arch: str
description: Optional[str]
description: Optional[str] = None
class FormatFeature(FeatureModel):
type: str = "format"
format: str
description: Optional[str]
description: Optional[str] = None
class MatchFeature(FeatureModel):
type: str = "match"
match: str
description: Optional[str]
description: Optional[str] = None
class CharacteristicFeature(FeatureModel):
type: str = "characteristic"
characteristic: str
description: Optional[str]
description: Optional[str] = None
class ExportFeature(FeatureModel):
type: str = "export"
export: str
description: Optional[str]
description: Optional[str] = None
class ImportFeature(FeatureModel):
type: str = "import"
import_: str = Field(alias="import")
description: Optional[str]
description: Optional[str] = None
class SectionFeature(FeatureModel):
type: str = "section"
section: str
description: Optional[str]
description: Optional[str] = None
class FunctionNameFeature(FeatureModel):
type: str = "function name"
function_name: str = Field(alias="function name")
description: Optional[str]
description: Optional[str] = None
class SubstringFeature(FeatureModel):
type: str = "substring"
substring: str
description: Optional[str]
description: Optional[str] = None
class RegexFeature(FeatureModel):
type: str = "regex"
regex: str
description: Optional[str]
description: Optional[str] = None
class StringFeature(FeatureModel):
type: str = "string"
string: str
description: Optional[str]
description: Optional[str] = None
class ClassFeature(FeatureModel):
type: str = "class"
class_: str = Field(alias="class")
description: Optional[str]
description: Optional[str] = None
class NamespaceFeature(FeatureModel):
type: str = "namespace"
namespace: str
description: Optional[str]
description: Optional[str] = None
class BasicBlockFeature(FeatureModel):
type: str = "basic block"
description: Optional[str]
description: Optional[str] = None
class APIFeature(FeatureModel):
type: str = "api"
api: str
description: Optional[str]
description: Optional[str] = None
class PropertyFeature(FeatureModel):
type: str = "property"
access: Optional[str]
access: Optional[str] = None
property: str
description: Optional[str]
description: Optional[str] = None
class NumberFeature(FeatureModel):
type: str = "number"
number: Union[int, float]
description: Optional[str]
description: Optional[str] = None
class BytesFeature(FeatureModel):
type: str = "bytes"
bytes: str
description: Optional[str]
description: Optional[str] = None
class OffsetFeature(FeatureModel):
type: str = "offset"
offset: int
description: Optional[str]
description: Optional[str] = None
class MnemonicFeature(FeatureModel):
type: str = "mnemonic"
mnemonic: str
description: Optional[str]
description: Optional[str] = None
class OperandNumberFeature(FeatureModel):
type: str = "operand number"
index: int
operand_number: int = Field(alias="operand number")
description: Optional[str]
description: Optional[str] = None
class OperandOffsetFeature(FeatureModel):
type: str = "operand offset"
index: int
operand_offset: int = Field(alias="operand offset")
description: Optional[str]
description: Optional[str] = None
Feature = Union[

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# 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
@@ -15,9 +15,9 @@ from capa.features.common import VALID_FEATURE_ACCESS, Feature
def hex(n: int) -> str:
"""render the given number using upper case hex, like: 0x123ABC"""
if n < 0:
return "-0x%X" % (-n)
return f"-0x{(-n):X}"
else:
return "0x%X" % n
return f"0x{(n):X}"
class API(Feature):
@@ -31,7 +31,7 @@ class _AccessFeature(Feature, abc.ABC):
super().__init__(value, description=description)
if access is not None:
if access not in VALID_FEATURE_ACCESS:
raise ValueError("%s access type %s not valid" % (self.name, access))
raise ValueError(f"{self.name} access type {access} not valid")
self.access = access
def __hash__(self):
@@ -53,6 +53,15 @@ class Property(_AccessFeature):
class Number(Feature):
def __init__(self, value: Union[int, float], description=None):
"""
args:
value (int or float): positive or negative integer, or floating point number.
the range of the value is:
- if positive, the range of u64
- if negative, the range of i64
- if floating, the range and precision of double
"""
super().__init__(value, description=description)
def get_value_str(self):
@@ -61,7 +70,7 @@ class Number(Feature):
elif isinstance(self.value, float):
return str(self.value)
else:
raise ValueError("invalid value type")
raise ValueError(f"invalid value type {type(self.value)}")
# max recognized structure size (and therefore, offset size)
@@ -70,6 +79,14 @@ MAX_STRUCTURE_SIZE = 0x10000
class Offset(Feature):
def __init__(self, value: int, description=None):
"""
args:
value (int): the offset, which can be positive or negative.
the range of the value is:
- if positive, the range of u64
- if negative, the range of i64
"""
super().__init__(value, description=description)
def get_value_str(self):
@@ -92,7 +109,7 @@ MAX_OPERAND_INDEX = MAX_OPERAND_COUNT - 1
class _Operand(Feature, abc.ABC):
# superclass: don't use directly
# subclasses should set self.name and provide the value string formatter
def __init__(self, index: int, value: int, description=None):
def __init__(self, index: int, value: Union[int, float], description=None):
super().__init__(value, description=description)
self.index = index
@@ -105,24 +122,45 @@ class _Operand(Feature, abc.ABC):
class OperandNumber(_Operand):
# cached names so we don't do extra string formatting every ctor
NAMES = ["operand[%d].number" % i for i in range(MAX_OPERAND_COUNT)]
NAMES = [f"operand[{i}].number" for i in range(MAX_OPERAND_COUNT)]
# operand[i].number: 0x12
def __init__(self, index: int, value: int, description=None):
def __init__(self, index: int, value: Union[int, float], description=None):
"""
args:
value (int or float): positive or negative integer, or floating point number.
the range of the value is:
- if positive, the range of u64
- if negative, the range of i64
- if floating, the range and precision of double
"""
super().__init__(index, value, description=description)
self.name = self.NAMES[index]
def get_value_str(self) -> str:
assert isinstance(self.value, int)
return hex(self.value)
if isinstance(self.value, int):
return capa.helpers.hex(self.value)
elif isinstance(self.value, float):
return str(self.value)
else:
raise ValueError("invalid value type")
class OperandOffset(_Operand):
# cached names so we don't do extra string formatting every ctor
NAMES = ["operand[%d].offset" % i for i in range(MAX_OPERAND_COUNT)]
NAMES = [f"operand[{i}].offset" for i in range(MAX_OPERAND_COUNT)]
# operand[i].offset: 0x12
def __init__(self, index: int, value: int, description=None):
"""
args:
value (int): the offset, which can be positive or negative.
the range of the value is:
- if positive, the range of u64
- if negative, the range of i64
"""
super().__init__(index, value, description=description)
self.name = self.NAMES[index]

172
capa/ghidra/README.md Normal file
View File

@@ -0,0 +1,172 @@
<div align="center">
<img src="/doc/img/ghidra_backend_logo.png" width=300 height=175>
</div>
The Ghidra feature extractor is an application of the FLARE team's open-source project, Ghidrathon, to integrate capa with Ghidra using Python 3. capa is a framework that uses a well-defined collection of rules to identify capabilities in a program. You can run capa against a PE file, ELF file, or shellcode and it tells you what it thinks the program can do. For example, it might suggest that the program is a backdoor, can install services, or relies on HTTP to communicate. The Ghidra feature extractor can be used to run capa analysis on your Ghidra databases without needing access to the original binary file.
<img src="/doc/img/ghidra_script_mngr_output.png">
## Getting Started
### Installation
Please ensure that you have the following dependencies installed before continuing:
| Dependency | Version | Source |
|------------|---------|--------|
| Ghidrathon | `>= 3.0.0` | https://github.com/mandiant/Ghidrathon |
| Python | `>= 3.8` | https://www.python.org/downloads |
| Ghidra | `>= 10.2` | https://ghidra-sre.org |
In order to run capa using using Ghidra, you must install capa as a library, obtain the official capa rules that match the capa version you have installed, and configure the Python 3 script [capa_ghidra.py](/capa/ghidra/capa_ghidra.py). You can do this by completing the following steps using the Python 3 interpreter that you have configured for your Ghidrathon installation:
1. Install capa and its dependencies from PyPI using the following command:
```bash
$ pip install flare-capa
```
2. Download and extract the [official capa rules](https://github.com/mandiant/capa-rules/releases) that match the capa version you have installed. Use the following command to view the version of capa you have installed:
```bash
$ pip show flare-capa
OR
$ capa --version
```
3. Copy [capa_ghidra.py](/capa/ghidra/capa_ghidra.py) to your `$USER_HOME/ghidra_scripts` directory or manually add `</path/to/ghidra_capa.py/>` to the Ghidra Script Manager.
## Usage
After completing the installation steps you can execute `capa_ghidra.py` using the Ghidra Script Manager or Headless Analyzer.
### Ghidra Script Manager
To execute `capa_ghidra.py` using the Ghidra Script Manager, first open the Ghidra Script Manager by navigating to `Window > Script Manager` in the Ghidra Code Browser. Next, locate `capa_ghidra.py` by selecting the `Python 3 > capa` category or using the Ghidra Script Manager search funtionality. Finally, double-click `capa_ghidra.py` to execute the script. If you don't see `capa_ghidra.py`, make sure you have copied the script to your `$USER_HOME/ghidra_scripts` directory or manually added `</path/to/ghidra_capa.py/>` to the Ghidra Script Manager
When executed, `capa_ghidra.py` asks you to provide your capa rules directory and preferred output format. `capa_ghidra.py` supports `default`, `verbose`, and `vverbose` output formats when executed from the Ghidra Script Manager. `capa_ghidra.py` writes output to the Ghidra Console Window.
#### Example
The following is an example of running `capa_ghidra.py` using the Ghidra Script Manager:
Selecting capa rules:
<img src="/doc/img/ghidra_script_mngr_rules.png">
Choosing output format:
<img src="/doc/img/ghidra_script_mngr_verbosity.png">
Viewing results in Ghidra Console Window:
<img src="/doc/img/ghidra_script_mngr_output.png">
### Ghidra Headless Analyzer
To execute `capa_ghidra.py` using the Ghidra Headless Analyzer, you can use the Ghidra `analyzeHeadless` script located in your `$GHIDRA_HOME/support` directory. You will need to provide the following arguments to the Ghidra `analyzeHeadless` script:
1. `</path/to/ghidra/project/>`: path to Ghidra project
2. `<ghidra_project_name>`: name of Ghidra Project
3. `-process <sample_name>`: name of sample `<sample_name>`
4. `-ScriptPath </path/to/capa_ghidra/>`: OPTIONAL argument specifying path `</path/to/capa_ghidra/>` to `capa_ghidra.py`
5. `-PostScript capa_ghidra.py`: executes `capa_ghidra.py` as post-analysis script
6. `"<capa_args>"`: single, quoted string containing capa arguments that must specify capa rules directory and output format, e.g. `"<path/to/capa/rules> --verbose"`. `capa_ghidra.py` supports `default`, `verbose`, `vverbose` and `json` formats when executed using the Ghidra Headless Analyzer. `capa_ghidra.py` writes output to the console window used to execute the Ghidra `analyzeHeadless` script.
7. `-processor <languageID>`: required ONLY if sample `<sample_name>` is shellcode. More information on specifying the `<languageID>` can be found in the `$GHIDRA_HOME/support/analyzeHeadlessREADME.html` documentation.
The following is an example of combining these arguments into a single `analyzeHeadless` script command:
```
$GHIDRA_HOME/support/analyzeHeadless </path/to/ghidra/project/> <ghidra_project_name> -process <sample_name> -PostScript capa_ghidra.py "/path/to/capa/rules/ --verbose"
```
You may also want to run capa against a sample that you have not yet imported into your Ghidra project. The following is an example of importing a sample and running `capa_ghidra.py` using a single `analyzeHeadless` script command:
```
$GHIDRA_HOME/support/analyzeHeadless </path/to/ghidra/project/> <ghidra_project_name> -Import </path/to/sample> -PostScript capa_ghidra.py "/path/to/capa/rules/ --verbose"
```
You can also provide `capa_ghidra.py` the single argument `"help"` to view supported arguments when running the script using the Ghidra Headless Analyzer:
```
$GHIDRA_HOME/support/analyzeHeadless </path/to/ghidra/project/> <ghidra_project_name> -process <sample_name> -PostScript capa_ghidra.py "help"
```
#### Example
The following is an example of running `capa_ghidra.py` against a shellcode sample using the Ghidra `analyzeHeadless` script:
```
$ analyzeHeadless /home/wumbo/Desktop/ghidra_projects/ capa_test -process 499c2a85f6e8142c3f48d4251c9c7cd6.raw32 -processor x86:LE:32:default -PostScript capa_ghidra.py "/home/wumbo/capa/rules -vv"
[...]
INFO REPORT: Analysis succeeded for file: /499c2a85f6e8142c3f48d4251c9c7cd6.raw32 (HeadlessAnalyzer)
INFO SCRIPT: /home/wumbo/ghidra_scripts/capa_ghidra.py (HeadlessAnalyzer)
md5 499c2a85f6e8142c3f48d4251c9c7cd6
sha1
sha256 e8e02191c1b38c808d27a899ac164b3675eb5cadd3a8907b0ffa863714000e72
path /home/wumbo/capa/tests/data/499c2a85f6e8142c3f48d4251c9c7cd6.raw32
timestamp 2023-08-29 17:57:00.946588
capa version 6.1.0
os unknown os
format Raw Binary
arch x86
extractor ghidra
base address global
rules /home/wumbo/capa/rules
function count 42
library function count 0
total feature count 1970
contain loop (24 matches, only showing first match of library rule)
author moritz.raabe@mandiant.com
scope function
function @ 0x0
or:
characteristic: loop @ 0x0
characteristic: tight loop @ 0x278
contain obfuscated stackstrings
namespace anti-analysis/obfuscation/string/stackstring
author moritz.raabe@mandiant.com
scope basic block
att&ck Defense Evasion::Obfuscated Files or Information::Indicator Removal from Tools [T1027.005]
mbc Anti-Static Analysis::Executable Code Obfuscation::Argument Obfuscation [B0032.020], Anti-Static Analysis::Executable Code Obfuscation::Stack Strings [B0032.017]
basic block @ 0x0 in function 0x0
characteristic: stack string @ 0x0
encode data using XOR
namespace data-manipulation/encoding/xor
author moritz.raabe@mandiant.com
scope basic block
att&ck Defense Evasion::Obfuscated Files or Information [T1027]
mbc Defense Evasion::Obfuscated Files or Information::Encoding-Standard Algorithm [E1027.m02], Data::Encode Data::XOR [C0026.002]
basic block @ 0x8AF in function 0x8A1
and:
characteristic: tight loop @ 0x8AF
characteristic: nzxor @ 0x8C0
not: = filter for potential false positives
or:
or: = unsigned bitwise negation operation (~i)
number: 0xFFFFFFFF = bitwise negation for unsigned 32 bits
number: 0xFFFFFFFFFFFFFFFF = bitwise negation for unsigned 64 bits
or: = signed bitwise negation operation (~i)
number: 0xFFFFFFF = bitwise negation for signed 32 bits
number: 0xFFFFFFFFFFFFFFF = bitwise negation for signed 64 bits
or: = Magic constants used in the implementation of strings functions.
number: 0x7EFEFEFF = optimized string constant for 32 bits
number: 0x81010101 = -0x81010101 = 0x7EFEFEFF
number: 0x81010100 = 0x81010100 = ~0x7EFEFEFF
number: 0x7EFEFEFEFEFEFEFF = optimized string constant for 64 bits
number: 0x8101010101010101 = -0x8101010101010101 = 0x7EFEFEFEFEFEFEFF
number: 0x8101010101010100 = 0x8101010101010100 = ~0x7EFEFEFEFEFEFEFF
get OS information via KUSER_SHARED_DATA
namespace host-interaction/os/version
author @mr-tz
scope function
att&ck Discovery::System Information Discovery [T1082]
references https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntexapi_x/kuser_shared_data/index.htm
function @ 0x1CA6
or:
number: 0x7FFE026C = NtMajorVersion @ 0x1D18
Script /home/wumbo/ghidra_scripts/capa_ghidra.py called exit with code 0
[...]
```

0
capa/ghidra/__init__.py Normal file
View File

167
capa/ghidra/capa_ghidra.py Normal file
View File

@@ -0,0 +1,167 @@
# Run capa against loaded Ghidra database
# @author Mike Hunhoff (mehunhoff@google.com)
# @category Python 3.capa
# 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 sys
import logging
import pathlib
import argparse
import capa
import capa.main
import capa.rules
import capa.ghidra.helpers
import capa.render.default
import capa.capabilities.common
import capa.features.extractors.ghidra.extractor
logger = logging.getLogger("capa_ghidra")
def run_headless():
parser = argparse.ArgumentParser(description="The FLARE team's open-source tool to integrate capa with Ghidra.")
parser.add_argument(
"rules",
type=str,
help="path to rule file or directory",
)
parser.add_argument(
"-v", "--verbose", action="store_true", help="enable verbose result document (no effect with --json)"
)
parser.add_argument(
"-vv", "--vverbose", action="store_true", help="enable very verbose result document (no effect with --json)"
)
parser.add_argument("-d", "--debug", action="store_true", help="enable debugging output on STDERR")
parser.add_argument("-q", "--quiet", action="store_true", help="disable all output but errors")
parser.add_argument("-j", "--json", action="store_true", help="emit JSON instead of text")
script_args = list(getScriptArgs()) # type: ignore [name-defined] # noqa: F821
if not script_args or len(script_args) > 1:
script_args = []
else:
script_args = script_args[0].split()
for idx, arg in enumerate(script_args):
if arg.lower() == "help":
script_args[idx] = "--help"
args = parser.parse_args(args=script_args)
if args.quiet:
logging.basicConfig(level=logging.WARNING)
logging.getLogger().setLevel(logging.WARNING)
elif args.debug:
logging.basicConfig(level=logging.DEBUG)
logging.getLogger().setLevel(logging.DEBUG)
else:
logging.basicConfig(level=logging.INFO)
logging.getLogger().setLevel(logging.INFO)
logger.debug("running in Ghidra headless mode")
rules_path = pathlib.Path(args.rules)
logger.debug("rule path: %s", rules_path)
rules = capa.main.get_rules([rules_path])
meta = capa.ghidra.helpers.collect_metadata([rules_path])
extractor = capa.features.extractors.ghidra.extractor.GhidraFeatureExtractor()
capabilities, counts = capa.capabilities.common.find_capabilities(rules, extractor, False)
meta.analysis.feature_counts = counts["feature_counts"]
meta.analysis.library_functions = counts["library_functions"]
meta.analysis.layout = capa.main.compute_layout(rules, extractor, capabilities)
if capa.capabilities.common.has_file_limitation(rules, capabilities, is_standalone=True):
logger.info("capa encountered warnings during analysis")
if args.json:
print(capa.render.json.render(meta, rules, capabilities)) # noqa: T201
elif args.vverbose:
print(capa.render.vverbose.render(meta, rules, capabilities)) # noqa: T201
elif args.verbose:
print(capa.render.verbose.render(meta, rules, capabilities)) # noqa: T201
else:
print(capa.render.default.render(meta, rules, capabilities)) # noqa: T201
return 0
def run_ui():
logging.basicConfig(level=logging.INFO)
logging.getLogger().setLevel(logging.INFO)
rules_dir: str = ""
try:
selected_dir = askDirectory("Choose capa rules directory", "Ok") # type: ignore [name-defined] # noqa: F821
if selected_dir:
rules_dir = selected_dir.getPath()
except RuntimeError:
# RuntimeError thrown when user selects "Cancel"
pass
if not rules_dir:
logger.info("You must choose a capa rules directory before running capa.")
return capa.main.E_MISSING_RULES
verbose = askChoice( # type: ignore [name-defined] # noqa: F821
"capa output verbosity", "Choose capa output verbosity", ["default", "verbose", "vverbose"], "default"
)
rules_path: pathlib.Path = pathlib.Path(rules_dir)
logger.info("running capa using rules from %s", str(rules_path))
rules = capa.main.get_rules([rules_path])
meta = capa.ghidra.helpers.collect_metadata([rules_path])
extractor = capa.features.extractors.ghidra.extractor.GhidraFeatureExtractor()
capabilities, counts = capa.capabilities.common.find_capabilities(rules, extractor, True)
meta.analysis.feature_counts = counts["feature_counts"]
meta.analysis.library_functions = counts["library_functions"]
meta.analysis.layout = capa.main.compute_layout(rules, extractor, capabilities)
if capa.capabilities.common.has_file_limitation(rules, capabilities, is_standalone=False):
logger.info("capa encountered warnings during analysis")
if verbose == "vverbose":
print(capa.render.vverbose.render(meta, rules, capabilities)) # noqa: T201
elif verbose == "verbose":
print(capa.render.verbose.render(meta, rules, capabilities)) # noqa: T201
else:
print(capa.render.default.render(meta, rules, capabilities)) # noqa: T201
return 0
def main():
if not capa.ghidra.helpers.is_supported_ghidra_version():
return capa.main.E_UNSUPPORTED_GHIDRA_VERSION
if not capa.ghidra.helpers.is_supported_file_type():
return capa.main.E_INVALID_FILE_TYPE
if not capa.ghidra.helpers.is_supported_arch_type():
return capa.main.E_INVALID_FILE_ARCH
if isRunningHeadless(): # type: ignore [name-defined] # noqa: F821
return run_headless()
else:
return run_ui()
if __name__ == "__main__":
if sys.version_info < (3, 8):
from capa.exceptions import UnsupportedRuntimeError
raise UnsupportedRuntimeError("This version of capa can only be used with Python 3.8+")
sys.exit(main())

160
capa/ghidra/helpers.py Normal file
View File

@@ -0,0 +1,160 @@
# 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 datetime
import contextlib
from typing import List
from pathlib import Path
import capa
import capa.version
import capa.features.common
import capa.features.freeze
import capa.render.result_document as rdoc
import capa.features.extractors.ghidra.helpers
logger = logging.getLogger("capa")
# file type as returned by Ghidra
SUPPORTED_FILE_TYPES = ("Executable and Linking Format (ELF)", "Portable Executable (PE)", "Raw Binary")
class GHIDRAIO:
"""
An object that acts as a file-like object,
using bytes from the current Ghidra listing.
"""
def __init__(self):
super().__init__()
self.offset = 0
self.bytes_ = self.get_bytes()
def seek(self, offset, whence=0):
assert whence == 0
self.offset = offset
def read(self, size):
logger.debug("reading 0x%x bytes at 0x%x (ea: 0x%x)", size, self.offset, currentProgram().getImageBase().add(self.offset).getOffset()) # type: ignore [name-defined] # noqa: F821
if size > len(self.bytes_) - self.offset:
logger.debug("cannot read 0x%x bytes at 0x%x (ea: BADADDR)", size, self.offset)
return b""
else:
return self.bytes_[self.offset : self.offset + size]
def close(self):
return
def get_bytes(self):
file_bytes = currentProgram().getMemory().getAllFileBytes()[0] # type: ignore [name-defined] # noqa: F821
# getOriginalByte() allows for raw file parsing on the Ghidra side
# other functions will fail as Ghidra will think that it's reading uninitialized memory
bytes_ = [file_bytes.getOriginalByte(i) for i in range(file_bytes.getSize())]
return capa.features.extractors.ghidra.helpers.ints_to_bytes(bytes_)
def is_supported_ghidra_version():
version = float(getGhidraVersion()[:4]) # type: ignore [name-defined] # noqa: F821
if version < 10.2:
warning_msg = "capa does not support this Ghidra version"
logger.warning(warning_msg)
logger.warning("Your Ghidra version is: %s. Supported versions are: Ghidra >= 10.2", version)
return False
return True
def is_running_headless():
return isRunningHeadless() # type: ignore [name-defined] # noqa: F821
def is_supported_file_type():
file_info = currentProgram().getExecutableFormat() # type: ignore [name-defined] # noqa: F821
if file_info not in SUPPORTED_FILE_TYPES:
logger.error("-" * 80)
logger.error(" Input file does not appear to be a supported file type.")
logger.error(" ")
logger.error(
" capa currently only supports analyzing PE, ELF, or binary files containing x86 (32- and 64-bit) shellcode."
)
logger.error(" If you don't know the input file type, you can try using the `file` utility to guess it.")
logger.error("-" * 80)
return False
return True
def is_supported_arch_type():
lang_id = str(currentProgram().getLanguageID()).lower() # type: ignore [name-defined] # noqa: F821
if not all((lang_id.startswith("x86"), any(arch in lang_id for arch in ("32", "64")))):
logger.error("-" * 80)
logger.error(" Input file does not appear to target a supported architecture.")
logger.error(" ")
logger.error(" capa currently only supports analyzing x86 (32- and 64-bit).")
logger.error("-" * 80)
return False
return True
def get_file_md5():
return currentProgram().getExecutableMD5() # type: ignore [name-defined] # noqa: F821
def get_file_sha256():
return currentProgram().getExecutableSHA256() # type: ignore [name-defined] # noqa: F821
def collect_metadata(rules: List[Path]):
md5 = get_file_md5()
sha256 = get_file_sha256()
info = currentProgram().getLanguageID().toString() # type: ignore [name-defined] # noqa: F821
if "x86" in info and "64" in info:
arch = "x86_64"
elif "x86" in info and "32" in info:
arch = "x86"
else:
arch = "unknown arch"
format_name: str = currentProgram().getExecutableFormat() # type: ignore [name-defined] # noqa: F821
if "PE" in format_name:
os = "windows"
elif "ELF" in format_name:
with contextlib.closing(capa.ghidra.helpers.GHIDRAIO()) as f:
os = capa.features.extractors.elf.detect_elf_os(f)
else:
os = "unknown os"
return rdoc.Metadata(
timestamp=datetime.datetime.now(),
version=capa.version.__version__,
argv=(),
sample=rdoc.Sample(
md5=md5,
sha1="",
sha256=sha256,
path=currentProgram().getExecutablePath(), # type: ignore [name-defined] # noqa: F821
),
flavor=rdoc.Flavor.STATIC,
analysis=rdoc.StaticAnalysis(
format=currentProgram().getExecutableFormat(), # type: ignore [name-defined] # noqa: F821
arch=arch,
os=os,
extractor="ghidra",
rules=tuple(r.resolve().absolute().as_posix() for r in rules),
base_address=capa.features.freeze.Address.from_capa(currentProgram().getImageBase().getOffset()), # type: ignore [name-defined] # noqa: F821
layout=rdoc.StaticLayout(
functions=(),
),
feature_counts=rdoc.StaticFeatureCounts(file=0, functions=()),
library_functions=(),
),
)

View File

@@ -1,19 +1,26 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# 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 os
import json
import inspect
import logging
import contextlib
import importlib.util
from typing import NoReturn
from pathlib import Path
import tqdm
from capa.exceptions import UnsupportedFormatError
from capa.features.common import FORMAT_PE, FORMAT_SC32, FORMAT_SC64, FORMAT_DOTNET, FORMAT_UNKNOWN, Format
from capa.features.common import FORMAT_PE, FORMAT_CAPE, FORMAT_SC32, FORMAT_SC64, FORMAT_DOTNET, FORMAT_UNKNOWN, Format
EXTENSIONS_SHELLCODE_32 = ("sc32", "raw32")
EXTENSIONS_SHELLCODE_64 = ("sc64", "raw64")
EXTENSIONS_DYNAMIC = ("json", "json_")
EXTENSIONS_ELF = "elf_"
logger = logging.getLogger("capa")
@@ -22,41 +29,62 @@ logger = logging.getLogger("capa")
def hex(n: int) -> str:
"""render the given number using upper case hex, like: 0x123ABC"""
if n < 0:
return "-0x%X" % (-n)
return f"-0x{(-n):X}"
else:
return "0x%X" % n
return f"0x{(n):X}"
def get_file_taste(sample_path: str) -> bytes:
if not os.path.exists(sample_path):
raise IOError("sample path %s does not exist or cannot be accessed" % sample_path)
with open(sample_path, "rb") as f:
taste = f.read(8)
def get_file_taste(sample_path: Path) -> bytes:
if not sample_path.exists():
raise IOError(f"sample path {sample_path} does not exist or cannot be accessed")
taste = sample_path.open("rb").read(8)
return taste
def is_runtime_ida():
return importlib.util.find_spec("idc") is not None
def is_runtime_ghidra():
try:
import idc
except ImportError:
currentProgram # type: ignore [name-defined] # noqa: F821
except NameError:
return False
else:
return True
return True
def assert_never(value: NoReturn) -> NoReturn:
assert False, f"Unhandled value: {value} ({type(value).__name__})"
def assert_never(value) -> NoReturn:
# careful: python -O will remove this assertion.
# but this is only used for type checking, so it's ok.
assert False, f"Unhandled value: {value} ({type(value).__name__})" # noqa: B011
def get_format_from_extension(sample: str) -> str:
if sample.endswith(EXTENSIONS_SHELLCODE_32):
return FORMAT_SC32
elif sample.endswith(EXTENSIONS_SHELLCODE_64):
return FORMAT_SC64
def get_format_from_report(sample: Path) -> str:
report = json.load(sample.open(encoding="utf-8"))
if "CAPE" in report:
return FORMAT_CAPE
if "target" in report and "info" in report and "behavior" in report:
# CAPE report that's missing the "CAPE" key,
# which is not going to be much use, but its correct.
return FORMAT_CAPE
return FORMAT_UNKNOWN
def get_auto_format(path: str) -> str:
def get_format_from_extension(sample: Path) -> str:
format_ = FORMAT_UNKNOWN
if sample.name.endswith(EXTENSIONS_SHELLCODE_32):
format_ = FORMAT_SC32
elif sample.name.endswith(EXTENSIONS_SHELLCODE_64):
format_ = FORMAT_SC64
elif sample.name.endswith(EXTENSIONS_DYNAMIC):
format_ = get_format_from_report(sample)
return format_
def get_auto_format(path: Path) -> str:
format_ = get_format(path)
if format_ == FORMAT_UNKNOWN:
format_ = get_format_from_extension(path)
@@ -65,17 +93,16 @@ def get_auto_format(path: str) -> str:
return format_
def get_format(sample: str) -> str:
def get_format(sample: Path) -> str:
# imported locally to avoid import cycle
from capa.features.extractors.common import extract_format
from capa.features.extractors.dnfile_ import DnfileFeatureExtractor
from capa.features.extractors.dotnetfile import DotnetFileFeatureExtractor
with open(sample, "rb") as f:
buf = f.read()
buf = sample.read_bytes()
for feature, _ in extract_format(buf):
if feature == Format(FORMAT_PE):
dnfile_extractor = DnfileFeatureExtractor(sample)
dnfile_extractor = DotnetFileFeatureExtractor(sample)
if dnfile_extractor.is_dotnet_file():
feature = Format(FORMAT_DOTNET)
@@ -85,17 +112,67 @@ def get_format(sample: str) -> str:
return FORMAT_UNKNOWN
@contextlib.contextmanager
def redirecting_print_to_tqdm(disable_progress):
"""
tqdm (progress bar) expects to have fairly tight control over console output.
so calls to `print()` will break the progress bar and make things look bad.
so, this context manager temporarily replaces the `print` implementation
with one that is compatible with tqdm.
via: https://stackoverflow.com/a/42424890/87207
"""
old_print = print # noqa: T202 [reserved word print used]
def new_print(*args, **kwargs):
# If tqdm.tqdm.write raises error, use builtin print
if disable_progress:
old_print(*args, **kwargs)
else:
try:
tqdm.tqdm.write(*args, **kwargs)
except Exception:
old_print(*args, **kwargs)
try:
# Globally replace print with new_print.
# Verified this works manually on Python 3.11:
# >>> import inspect
# >>> inspect.builtins
# <module 'builtins' (built-in)>
inspect.builtins.print = new_print # type: ignore
yield
finally:
inspect.builtins.print = old_print # type: ignore
def log_unsupported_format_error():
logger.error("-" * 80)
logger.error(" Input file does not appear to be a PE or ELF file.")
logger.error(" Input file does not appear to be a supported file.")
logger.error(" ")
logger.error(
" capa currently only supports analyzing PE and ELF files (or shellcode, when using --format sc32|sc64)."
)
logger.error(" See all supported file formats via capa's help output (-h).")
logger.error(" If you don't know the input file type, you can try using the `file` utility to guess it.")
logger.error("-" * 80)
def log_unsupported_cape_report_error(error: str):
logger.error("-" * 80)
logger.error("Input file is not a valid CAPE report: %s", error)
logger.error(" ")
logger.error(" capa currently only supports analyzing standard CAPE reports in JSON format.")
logger.error(
" Please make sure your report file is in the standard format and contains both the static and dynamic sections."
)
logger.error("-" * 80)
def log_empty_cape_report_error(error: str):
logger.error("-" * 80)
logger.error(" CAPE report is empty or only contains little useful data: %s", error)
logger.error(" ")
logger.error(" Please make sure the sandbox run captures useful behaviour of your sample.")
logger.error("-" * 80)
def log_unsupported_os_error():
logger.error("-" * 80)
logger.error(" Input file does not appear to target a supported OS.")
@@ -118,7 +195,7 @@ def log_unsupported_runtime_error():
logger.error("-" * 80)
logger.error(" Unsupported runtime or Python interpreter.")
logger.error(" ")
logger.error(" capa supports running under Python 3.7 and higher.")
logger.error(" capa supports running under Python 3.8 and higher.")
logger.error(" ")
logger.error(
" If you're seeing this message on the command line, please ensure you're running a supported Python version."

View File

@@ -1,15 +1,15 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# 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 json
import logging
import datetime
import contextlib
from typing import Optional
from typing import List, Optional
from pathlib import Path
import idc
import idaapi
@@ -22,7 +22,8 @@ import capa
import capa.version
import capa.render.utils as rutils
import capa.features.common
import capa.render.result_document
import capa.features.freeze
import capa.render.result_document as rdoc
from capa.features.address import AbsoluteVirtualAddress
logger = logging.getLogger("capa")
@@ -45,7 +46,8 @@ NETNODE_RULES_CACHE_ID = "rules-cache-id"
def inform_user_ida_ui(message):
idaapi.info("%s. Please refer to IDA Output window for more information." % message)
# this isn't a logger, this is IDA's logging facility
idaapi.info(f"{message}. Please refer to IDA Output window for more information.") # noqa: G004
def is_supported_ida_version():
@@ -53,7 +55,7 @@ def is_supported_ida_version():
if version < 7.4 or version >= 9:
warning_msg = "This plugin does not support your IDA Pro version"
logger.warning(warning_msg)
logger.warning("Your IDA Pro version is: %s. Supported versions are: IDA >= 7.4 and IDA < 9.0." % version)
logger.warning("Your IDA Pro version is: %s. Supported versions are: IDA >= 7.4 and IDA < 9.0.", version)
return False
return True
@@ -118,7 +120,7 @@ def get_file_sha256():
return sha256
def collect_metadata(rules):
def collect_metadata(rules: List[Path]):
""" """
md5 = get_file_md5()
sha256 = get_file_sha256()
@@ -140,37 +142,36 @@ def collect_metadata(rules):
else:
os = "unknown os"
return {
"timestamp": datetime.datetime.now().isoformat(),
"argv": [],
"sample": {
"md5": md5,
"sha1": "", # not easily accessible
"sha256": sha256,
"path": idaapi.get_input_file_path(),
},
"analysis": {
"format": idaapi.get_file_type_name(),
"arch": arch,
"os": os,
"extractor": "ida",
"rules": rules,
"base_address": idaapi.get_imagebase(),
"layout": {
return rdoc.Metadata(
timestamp=datetime.datetime.now(),
version=capa.version.__version__,
argv=(),
sample=rdoc.Sample(
md5=md5,
sha1="", # not easily accessible
sha256=sha256,
path=idaapi.get_input_file_path(),
),
flavor=rdoc.Flavor.STATIC,
analysis=rdoc.StaticAnalysis(
format=idaapi.get_file_type_name(),
arch=arch,
os=os,
extractor="ida",
rules=tuple(r.resolve().absolute().as_posix() for r in rules),
base_address=capa.features.freeze.Address.from_capa(idaapi.get_imagebase()),
layout=rdoc.StaticLayout(
functions=(),
# this is updated after capabilities have been collected.
# will look like:
#
# "functions": { 0x401000: { "matched_basic_blocks": [ 0x401000, 0x401005, ... ] }, ... }
},
),
# ignore these for now - not used by IDA plugin.
"feature_counts": {
"file": {},
"functions": {},
},
"library_functions": {},
},
"version": capa.version.__version__,
}
feature_counts=rdoc.StaticFeatureCounts(file=0, functions=()),
library_functions=(),
),
)
class IDAIO:
@@ -213,16 +214,16 @@ def idb_contains_cached_results() -> bool:
n = netnode.Netnode(CAPA_NETNODE)
return bool(n.get(NETNODE_RESULTS))
except netnode.NetnodeCorruptError as e:
logger.error("%s", e, exc_info=True)
logger.exception(str(e))
return False
def load_and_verify_cached_results() -> Optional[capa.render.result_document.ResultDocument]:
def load_and_verify_cached_results() -> Optional[rdoc.ResultDocument]:
"""verifies that cached results have valid (mapped) addresses for the current database"""
logger.debug("loading cached capa results from netnode '%s'", CAPA_NETNODE)
n = netnode.Netnode(CAPA_NETNODE)
doc = capa.render.result_document.ResultDocument.parse_obj(json.loads(n[NETNODE_RESULTS]))
doc = rdoc.ResultDocument.model_validate_json(n[NETNODE_RESULTS])
for rule in rutils.capability_rules(doc):
for location_, _ in rule.matches:

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