Compare commits

...

2297 Commits

Author SHA1 Message Date
Moritz
c2346f41cb update to v5.0.0 (#1308) 2023-02-08 21:34:45 +01:00
Capa Bot
3f40f47104 Sync capa rules submodule 2023-02-08 08:57:54 +00:00
Capa Bot
3dfb7beb6b Sync capa rules submodule 2023-02-07 15:56:56 +00:00
Moritz
6a222a6139 Update black (#1307)
* build(deps-dev): bump black from 22.12.0 to 23.1.0

Bumps [black](https://github.com/psf/black) from 22.12.0 to 23.1.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/22.12.0...23.1.0)

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

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

* reformat black 23.1.0

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-07 15:50:15 +01:00
Capa Bot
b34864c55e Sync capa rules submodule 2023-02-07 14:49:39 +00:00
Capa Bot
26655315c7 Sync capa rules submodule 2023-02-07 14:48:39 +00:00
Capa Bot
8aaa8809e6 Sync capa-testfiles submodule 2023-02-07 11:21:49 +00:00
Capa Bot
cbac0e0d3b Sync capa rules submodule 2023-02-07 09:59:16 +00:00
Capa Bot
22b8c594b8 Sync capa-testfiles submodule 2023-02-06 20:47:00 +00:00
Capa Bot
7a8065b2bb Sync capa rules submodule 2023-02-06 17:13:11 +00:00
Capa Bot
6070479e0a Sync capa rules submodule 2023-02-06 17:12:33 +00:00
Moritz
fd70dc24df feat: store results to database and UI updates (#1292)
* feat: store results to database and UI updates

* feat: update result caching and UI

* use system rules cache and improve result cache validation

* improve buttons and status messages

* improve error messaging for invalid caches

---------

Co-authored-by: Mike Hunhoff <mike.hunhoff@gmail.com>
2023-02-06 16:37:19 +01:00
Capa Bot
8cb8cfdb46 Sync capa-testfiles submodule 2023-02-06 15:21:58 +00:00
Capa Bot
79f25ec0a3 Sync capa rules submodule 2023-02-06 14:15:55 +00:00
Capa Bot
2235417a25 Sync capa-testfiles submodule 2023-02-06 14:07:24 +00:00
Capa Bot
ce449790df Sync capa-testfiles submodule 2023-02-06 14:03:55 +00:00
Capa Bot
79e36ab11d Sync capa-testfiles submodule 2023-02-06 13:52:53 +00:00
Capa Bot
dde3abdfa0 Sync capa-testfiles submodule 2023-02-06 09:07:31 +00:00
Mike Hunhoff
7ea166f98c explorer: fix UnboundLocal errors and improve render match by function (#1302) 2023-02-02 12:33:30 -07:00
Capa Bot
faceca6fec Sync capa rules submodule 2023-02-02 08:12:15 +00:00
Capa Bot
6589b2044b Sync capa rules submodule 2023-02-01 15:29:00 +00:00
Capa Bot
f00e44aba6 Sync capa-testfiles submodule 2023-02-01 15:28:22 +00:00
Capa Bot
6591b574a0 Sync capa rules submodule 2023-02-01 14:13:20 +00:00
Moritz
ca91051d1a Fix string length >= 4 and remove bytes/string overlaps (#1298)
* fix min string length >= 4

* feat: don't extract bytes for strings
2023-02-01 14:53:16 +01:00
Capa Bot
29f24de5d5 Sync capa rules submodule 2023-02-01 09:10:08 +00:00
Capa Bot
2014c64732 Sync capa rules submodule 2023-02-01 09:09:30 +00:00
Moritz
b5c6cdeaa1 Update ATT&CK and MBC lint data (#1297)
* sort by ID

* update ATT&CK/MBC lint data via script
2023-02-01 09:56:10 +01:00
Moritz
bf7c569060 Delete hook-smda.py (#1296) 2023-01-30 10:15:56 +01:00
Capa Bot
bbc0afd083 Sync capa rules submodule 2023-01-27 08:56:49 +00:00
Capa Bot
8857f92f7c Sync capa rules submodule 2023-01-26 08:15:31 +00:00
Willi Ballenthin
70f568b1cc Merge pull request #1291 from mandiant/rules-cache
cache rule set across invocations of capa
2023-01-25 17:52:34 +01:00
Capa Bot
c586166006 Sync capa-testfiles submodule 2023-01-25 16:45:08 +00:00
Moritz
96f266ce5e ci: pin GitHub Actions versions (#1295) 2023-01-25 17:34:28 +01:00
Willi Ballenthin
e5549d6ce8 Update capa/ida/plugin/form.py 2023-01-25 16:47:01 +01:00
Capa Bot
b60717bb8c Sync capa rules submodule 2023-01-24 14:35:01 +00:00
Willi Ballenthin
83eefd343c Update scripts/capa2yara.py
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2023-01-24 15:33:37 +01:00
Moritz
03e8be6368 Create scorecard.yml (#1294) 2023-01-24 14:15:53 +01:00
Capa Bot
a58e9e4df3 Sync capa rules submodule 2023-01-23 13:53:42 +00:00
Moritz
0a78187c69 optimize tests to speed them up (#1287)
* optimize tests to speed them up

Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2023-01-23 11:25:04 +01:00
Willi Ballenthin
61112c2527 lint: fix pbar counts 2023-01-21 20:16:49 +01:00
Willi Ballenthin
67cfefd2df main: get_rules: remove progress bar 2023-01-21 19:38:23 +01:00
Willi Ballenthin
3dfd16c033 main: fix ValueError 2023-01-21 19:30:15 +01:00
Willi Ballenthin
67b9d2e1c0 black 2023-01-21 19:28:15 +01:00
Willi Ballenthin
a076a0c44e main: further document get_rules 2023-01-21 19:24:20 +01:00
Willi Ballenthin
f152729c79 explorer: use main.get_rules and simplify cache 2023-01-21 19:10:50 +01:00
Willi Ballenthin
3c0e36d5d4 ruleset: record number of source rules loaded 2023-01-21 19:10:35 +01:00
Willi Ballenthin
887f37b72c main: get_rules: accept callback to update status 2023-01-21 19:10:02 +01:00
Willi Ballenthin
e30dd08dec cache: add doc 2023-01-21 18:20:14 +01:00
Willi Ballenthin
2d1bbeda0c Merge branch 'rules-cache' of personal.github.com:mandiant/capa into rules-cache 2023-01-21 18:14:42 +01:00
Willi Ballenthin
68603a9cc7 Update scripts/cache-ruleset.py
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2023-01-23 12:13:07 +01:00
Willi Ballenthin
6c83db9977 Update scripts/cache-ruleset.py
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2023-01-23 12:12:57 +01:00
Willi Ballenthin
6d16cafbc8 cache: handle invalid caches 2023-01-21 18:14:12 +01:00
Willi Ballenthin
e503cedd8f main: pbar: realize the list so it has a length 2023-01-21 17:31:57 +01:00
Willi Ballenthin
1a498d1afc main: fix reference error 2023-01-20 16:21:44 +01:00
Willi Ballenthin
33a46cc633 ci: cache the ruleset 2023-01-20 16:19:46 +01:00
Willi Ballenthin
b3b9ec11dd pyinstaller: package up the cache directory, too 2023-01-20 16:11:00 +01:00
Willi Ballenthin
a7afdec2e1 cache: accept cache_dir parameter 2023-01-20 16:10:41 +01:00
Willi Ballenthin
56a0bedac9 scripts: add tool to cache a ruleset to a directory 2023-01-20 15:50:17 +01:00
Willi Ballenthin
f451fe68e1 pep8/mypy 2023-01-20 15:42:22 +01:00
Willi Ballenthin
946816e377 cache: improve variable name 2023-01-20 15:26:17 +01:00
Willi Ballenthin
99af09fce5 main: revert wording change, which was just churn 2023-01-20 15:24:34 +01:00
Willi Ballenthin
0888e5ad69 main: more doc 2023-01-20 15:22:43 +01:00
Willi Ballenthin
c423ccec67 add tests for ruleset caching 2023-01-20 15:20:26 +01:00
Willi Ballenthin
03f72f498e cache: use zlib to reduce cache size 2023-01-20 15:20:10 +01:00
Willi Ballenthin
fbd7c566f4 cache: add more helpers
to enable better testing
2023-01-20 15:19:48 +01:00
Willi Ballenthin
e09d35bbb9 main: fix rule content decoding 2023-01-20 15:01:05 +01:00
Willi Ballenthin
e644775ad1 changelog 2023-01-20 14:52:47 +01:00
Willi Ballenthin
6ad471a914 Merge branch 'master' into rules-cache 2023-01-20 14:51:32 +01:00
Willi Ballenthin
476ffabae9 rules: cache the ruleset to disk
ref: #1212
2023-01-20 14:50:00 +01:00
Willi Ballenthin
4b7a9e149f rules: move to directory structure 2023-01-20 13:27:30 +01:00
Capa Bot
49c18bd83d Sync capa rules submodule 2023-01-20 12:15:23 +00:00
Capa Bot
67717761bd Sync capa rules submodule 2023-01-20 12:15:02 +00:00
Capa Bot
b10196cdac Sync capa rules submodule 2023-01-20 11:12:04 +00:00
Moritz
fa0ddba436 add format to global features and code refactors (#1284)
* refactor: get format handling

* add format to global features
2023-01-19 13:31:00 +01:00
Capa Bot
0fb3be359f Sync capa rules submodule 2023-01-19 12:12:41 +00:00
Capa Bot
26662e99de Sync capa rules submodule 2023-01-19 12:11:19 +00:00
Willi Ballenthin
5513d4ca43 viv: insn: string: handle viv bug around substrings (#1273)
* viv: insn: string: handle viv bug around substrings

closes #1271

* use minimum string length 4

* update overlapping string test and fixup vivisect elf analysis missing function

Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2023-01-19 13:02:53 +01:00
Capa Bot
2b07ec925c Sync capa rules submodule 2023-01-19 11:23:42 +00:00
Capa Bot
efb4c9d540 Sync capa rules submodule 2023-01-19 10:58:26 +00:00
Moritz
b8de9625ee fix: don't extract invalid calls from features (#1285) 2023-01-19 11:56:13 +01:00
Willi Ballenthin
607daa345e Merge pull request #1288 from mandiant/dependabot/pip/wcwidth-0.2.6
build(deps): bump wcwidth from 0.2.5 to 0.2.6
2023-01-19 11:43:35 +01:00
Capa Bot
35e6df6f6b Sync capa rules submodule 2023-01-18 15:10:43 +00:00
dependabot[bot]
cb1ef965d0 build(deps): bump wcwidth from 0.2.5 to 0.2.6
Bumps [wcwidth](https://github.com/jquast/wcwidth) from 0.2.5 to 0.2.6.
- [Release notes](https://github.com/jquast/wcwidth/releases)
- [Commits](https://github.com/jquast/wcwidth/compare/0.2.5...0.2.6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-16 14:03:54 +00:00
Capa Bot
2ab057a24d Sync capa rules submodule 2023-01-12 13:15:35 +00:00
Capa Bot
12f8588c03 Sync capa-testfiles submodule 2023-01-12 12:59:01 +00:00
Capa Bot
3571f35578 Sync capa rules submodule 2023-01-12 11:57:41 +00:00
Willi Ballenthin
803fe321d1 Merge pull request #1283 from mandiant/fix/issue-1282
better detect invalid rules
2023-01-12 12:56:25 +01:00
Willi Ballenthin
cf42670e97 Merge branch 'master' into fix/issue-1282 2023-01-12 12:31:11 +01:00
Willi Ballenthin
ac36b9d328 changelog 2023-01-12 10:39:36 +01:00
Willi Ballenthin
9a9f72f07a pep8 2023-01-12 10:38:52 +01:00
Willi Ballenthin
4b9a844c92 rules: catch invalid YAML exception 2023-01-12 10:38:26 +01:00
Moritz
a273ad31d4 make read consistent with file object behavior (#1281) 2023-01-11 17:17:04 +01:00
Willi Ballenthin
16f3164865 Merge pull request #1280 from mandiant/revert-1275-dependabot/pip/networkx-3.0
Revert "build(deps): bump networkx from 2.5.1 to 3.0"
2023-01-11 12:16:47 +01:00
Willi Ballenthin
5fb9de775f setup: document networkx dep version pin 2023-01-11 10:50:55 +01:00
Willi Ballenthin
05879dc02a Revert "build(deps): bump networkx from 2.5.1 to 3.0" 2023-01-11 10:49:04 +01:00
Willi Ballenthin
d5cb36151f Merge pull request #1275 from mandiant/dependabot/pip/networkx-3.0
build(deps): bump networkx from 2.5.1 to 3.0
2023-01-10 16:52:45 +01:00
Moritz
b6fd95c7b8 use positive error return code numbers (#1274) 2023-01-10 13:14:23 +01:00
Willi Ballenthin
8ce570cea7 Merge pull request #1276 from mandiant/dependabot/pip/termcolor-2.2.0
build(deps): bump termcolor from 2.1.1 to 2.2.0
2023-01-10 12:25:01 +01:00
Willi Ballenthin
5b82ed2fd9 Merge pull request #1270 from mandiant/fix/issue-1267
features: string: better __str__ embedded whitespace
2023-01-10 12:21:27 +01:00
Capa Bot
37a4dbf822 Sync capa rules submodule 2023-01-09 15:53:03 +00:00
dependabot[bot]
ef86160d88 build(deps): bump termcolor from 2.1.1 to 2.2.0
Bumps [termcolor](https://github.com/termcolor/termcolor) from 2.1.1 to 2.2.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.1.1...2.2.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-09 14:03:48 +00:00
dependabot[bot]
5f31bdbb3e build(deps): bump networkx from 2.5.1 to 3.0
Bumps [networkx](https://github.com/networkx/networkx) from 2.5.1 to 3.0.
- [Release notes](https://github.com/networkx/networkx/releases)
- [Commits](https://github.com/networkx/networkx/compare/networkx-2.5.1...networkx-3.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-09 14:03:44 +00:00
Capa Bot
810e2d70d3 Sync capa rules submodule 2023-01-09 13:38:25 +00:00
Moritz
85dd065f91 only show first lib match to reduce vverbose output noise (#1266)
* only show first lib match to reduce vverbose output noise

* improve rendering and wording
2023-01-09 14:14:08 +01:00
Capa Bot
2a61e357de Sync capa rules submodule 2023-01-09 13:08:27 +00:00
Willi Ballenthin
e34fdfae1a mypy 2023-01-09 13:01:41 +01:00
Willi Ballenthin
58e94a35cb features: string: better __str__ embedded whitespace 2023-01-09 10:51:08 +01:00
Capa Bot
93acf9feb4 Sync capa rules submodule 2023-01-09 08:50:03 +00:00
Moritz
0362148989 Merge pull request #1265 from mandiant/fix/extractor-logic
fix logic error from smda backend removal
2023-01-06 09:54:52 +01:00
mr-tz
985ea5ebdc fix logic error from smda backend removal 2023-01-05 12:27:27 +01:00
Capa Bot
64ebf14256 Sync capa rules submodule 2023-01-05 10:55:44 +00:00
Willi Ballenthin
cfebe5a5ba Merge pull request #1264 from mandiant/fix/issue-1263
render: verbose: fix rendering of scopes
2023-01-05 11:54:59 +01:00
Willi Ballenthin
99e0e45bfc changelog 2023-01-05 11:38:51 +01:00
Willi Ballenthin
83845078a7 render: verbose: fix rendering of scopes
closes #1263
2023-01-05 11:36:52 +01:00
Capa Bot
7c102509bd Sync capa rules submodule 2023-01-05 09:59:07 +00:00
Capa Bot
1af90b9db3 Sync capa rules submodule 2023-01-05 09:55:12 +00:00
Mike Hunhoff
d4de650f90 explorer: improve exception handling (#1262) 2023-01-04 13:28:15 -07:00
Capa Bot
5de0324441 Sync capa rules submodule 2023-01-04 16:59:55 +00:00
Moritz
5fa2a87747 fix dotnet and pe format handling (#1256) 2023-01-04 17:46:51 +01:00
Moritz
68ef9d7858 validate rule meta (#1257)
* validate rule meta
2023-01-04 17:46:25 +01:00
Mike Hunhoff
a286e066d1 explorer: refactor rule generator caching and matching (#1251)
* explorer: refactor rule generator caching and matching

* fix #1246

* fix #1159
2023-01-04 08:50:52 -07:00
Willi Ballenthin
94a712b820 Merge pull request #1213 from mandiant/fix-1062
remove SMDA backend
2023-01-04 14:48:41 +01:00
Moritz
c8aa73ac18 Merge pull request #1253 from mandiant/dependabot/pip/pydantic-1.10.4
build(deps): bump pydantic from 1.10.2 to 1.10.4
2023-01-04 11:17:31 +01:00
Capa Bot
a74b8e6328 Sync capa-testfiles submodule 2023-01-04 09:09:57 +00:00
Willi Ballenthin
ff773695d0 Merge pull request #1260 from jsoref/spelling
Spelling
2023-01-04 08:58:21 +01:00
Josh Soref
c4ebb0a31d spelling: unescaped
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-01-04 00:32:39 -05:00
Josh Soref
f9b3d6304c spelling: uncommitted
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-01-04 00:32:39 -05:00
Josh Soref
1c85f530b1 spelling: objects
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-01-04 00:32:39 -05:00
Josh Soref
d65d7bcd7e spelling: notifications
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-01-04 00:32:39 -05:00
Josh Soref
c11633c5db spelling: minimum
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-01-04 00:32:39 -05:00
Josh Soref
ea0a708f35 spelling: interesting
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-01-04 00:32:39 -05:00
Josh Soref
00254b93dc spelling: instruction
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-01-04 00:32:39 -05:00
Josh Soref
6932df3564 spelling: import
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-01-04 00:32:39 -05:00
Josh Soref
9e3a48aa8d spelling: globally
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-01-04 00:32:39 -05:00
Josh Soref
6e17462bd0 spelling: github
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-01-04 00:32:39 -05:00
Josh Soref
d29e7e6f3a spelling: further
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-01-04 00:32:39 -05:00
Josh Soref
049e222e88 spelling: falls through
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-01-04 00:32:39 -05:00
Josh Soref
caef7812a3 spelling: disassembly
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-01-04 00:32:39 -05:00
Josh Soref
68efa7316b spelling: dictionary
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-01-04 00:25:22 -05:00
Josh Soref
5396d5f99e spelling: contiguous
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-01-04 00:25:22 -05:00
Josh Soref
4576cbd0a1 spelling: committing
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-01-04 00:25:22 -05:00
Josh Soref
1fa9180fee spelling: beginning
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-01-04 00:25:22 -05:00
Josh Soref
801c80d7a2 spelling: alphanum
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-01-04 00:25:22 -05:00
mr-tz
eba1989c9f Merge branch 'master' into fix-1062 2023-01-03 18:46:41 +01:00
Mike Hunhoff
90591811df explorer: improve rules error messaging and documentation (#1249) 2023-01-03 09:09:05 -07:00
Capa Bot
c959506ae9 Sync capa rules submodule 2023-01-03 14:58:40 +00:00
Moritz
25f9029a82 Merge pull request #1255 from mandiant/ci/update-actions
update Actions
2023-01-03 11:56:58 +01:00
Capa Bot
4f75b3d9f6 Sync capa rules submodule 2023-01-03 10:46:49 +00:00
Capa Bot
974d79f2be Sync capa rules submodule 2023-01-03 10:42:41 +00:00
mr-tz
c0a8a91281 update Actions 2023-01-03 11:39:51 +01:00
Capa Bot
2219139605 Sync capa-testfiles submodule 2023-01-03 10:20:18 +00:00
Capa Bot
966e38babf Sync capa rules submodule 2023-01-03 10:19:17 +00:00
Capa Bot
5f39083df6 Sync capa-testfiles submodule 2023-01-03 10:17:36 +00:00
Capa Bot
565b002bfe Sync capa rules submodule 2023-01-02 17:33:19 +00:00
Capa Bot
1dd5a8dbf2 Sync capa rules submodule 2023-01-02 17:31:53 +00:00
dependabot[bot]
7ef17b8dee build(deps): bump pydantic from 1.10.2 to 1.10.4
Bumps [pydantic](https://github.com/pydantic/pydantic) from 1.10.2 to 1.10.4.
- [Release notes](https://github.com/pydantic/pydantic/releases)
- [Changelog](https://github.com/pydantic/pydantic/blob/v1.10.4/HISTORY.md)
- [Commits](https://github.com/pydantic/pydantic/compare/v1.10.2...v1.10.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-02 14:05:03 +00:00
Moritz
d01a0e022d Merge pull request #1248 from mandiant/dependabot/pip/isort-5.11.4
build(deps-dev): bump isort from 5.11.3 to 5.11.4
2023-01-02 13:22:31 +01:00
Moritz
3258556d5d Merge pull request #1247 from mandiant/doc/rule-compat-info
update rule compatibility doc
2023-01-02 13:21:53 +01:00
Mike Hunhoff
5f77200108 explorer: assume 32-bit displacement for offsets (#1250)
* explorer: assume 32-bit displacement for offsets
2022-12-29 07:08:10 -07:00
dependabot[bot]
b12865f1e5 build(deps-dev): bump isort from 5.11.3 to 5.11.4
Bumps [isort](https://github.com/pycqa/isort) from 5.11.3 to 5.11.4.
- [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.3...5.11.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-26 14:04:30 +00:00
mr-tz
ee90fc8761 update rule compatibility doc 2022-12-23 18:30:25 +01:00
Moritz
e6585ee526 Merge pull request #1245 from mandiant/doc/rule-releases
simplified rule release guidance
2022-12-22 15:37:06 +01:00
Mike Hunhoff
b68be0c2ce dotnet: emit namespace/class features for type references (#1242)
* dotnet: emit namespace/class features for type references

* dotnet: pre-compute .NET token caches
2022-12-21 15:59:29 -07:00
mr-tz
3b95ed0b5a simplified rule release guidance 2022-12-21 16:03:05 +01:00
Mike Hunhoff
50490e6a93 dotnet: emit namespace/class features for ldvirtftn/ldftn instructions (#1241)
* dotnet: emit namespace/class features for ldvirtftn/ldftn instructions

* dotnet: add unit tests for ldftn/ldvirtftn namespace/class features
2022-12-20 13:29:29 -07:00
Willi Ballenthin
d466345e4e Merge pull request #1239 from mandiant/dependabot/pip/isort-5.11.3
build(deps-dev): bump isort from 5.10.1 to 5.11.3
2022-12-20 13:42:24 +01:00
Mike Hunhoff
4ece47c64c dotnet: emit calls to/from MethodDef methods (#1236)
* dotnet: emit calls to/from MethodDef methods

* dotnet: update function.py copyright header
2022-12-19 15:06:16 -07:00
Moritz
2b85af0f88 explorer: update and remove outdated documentation (#1238) 2022-12-19 14:53:16 -07:00
Mike Hunhoff
e0491097b0 dotnet: emit API features for generic methods (#1231)
* dotnet: emit API features for generic methods

* dotnet: improve type checking

* dotnet: emit namespace/class features for generic methods

* dotnet: update for dnfile 0.13.0

* dotnet: refactor property extraction
2022-12-19 14:45:21 -07:00
dependabot[bot]
fa3d658f33 build(deps): bump dnfile from 0.12.0 to 0.13.0 (#1240)
Bumps [dnfile](https://github.com/malwarefrank/dnfile) from 0.12.0 to 0.13.0.
- [Release notes](https://github.com/malwarefrank/dnfile/releases)
- [Changelog](https://github.com/malwarefrank/dnfile/blob/master/HISTORY.rst)
- [Commits](https://github.com/malwarefrank/dnfile/compare/v0.12.0...v0.13.0)

---
updated-dependencies:
- dependency-name: dnfile
  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>
2022-12-19 10:43:54 -07:00
dependabot[bot]
6dcd115765 build(deps-dev): bump isort from 5.10.1 to 5.11.3
Bumps [isort](https://github.com/pycqa/isort) from 5.10.1 to 5.11.3.
- [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.10.1...5.11.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-19 14:02:58 +00:00
Willi Ballenthin
88cffee902 ci: bump action versions (#1233)
* ci: bump action versions

Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2022-12-19 12:34:18 +01:00
Willi Ballenthin
b12d526a60 tests: use python 3.11 (#1191) 2022-12-19 11:12:42 +01:00
Mike Hunhoff
3af7fe0b08 dotnet: address unhandled exceptions through improved type checking (#1230)
* dotnet: bump dncil version

* dotnet: check #US stream valid before access

* dotnet: use assert statements to guard types
2022-12-15 12:55:57 -07:00
Willi Ballenthin
d7548c0b20 Merge pull request #1229 from mandiant/williballenthin-patch-2
setup: viv-utils 0.7.7
2022-12-15 12:03:48 +01:00
Willi Ballenthin
f79e16d1a6 Merge branch 'master' of https://github.com/mandiant/capa into williballenthin-patch-2 2022-12-15 10:07:36 +00:00
Willi Ballenthin
ad47ea3bab Merge pull request #1235 from mandiant/fix/issue-1234
stricter mypy checking
2022-12-15 10:54:03 +01:00
Willi Ballenthin
505910edb5 dotnet: remove duplicative validate_has_dotnet helper 2022-12-14 21:28:32 +01:00
Willi Ballenthin
aee0ec8016 features: cleanup mypy checking 2022-12-14 21:22:52 +01:00
Willi Ballenthin
613c185428 tests: fix broken test 2022-12-14 11:51:25 +01:00
Willi Ballenthin
501227f23f elf: fix missing attribute 2022-12-14 11:14:01 +01:00
Willi Ballenthin
56d075fd32 typing 2022-12-14 11:08:46 +01:00
Willi Ballenthin
9ae908c741 elf: better format attribution declarations 2022-12-14 10:57:27 +01:00
Willi Ballenthin
81500a4d1d black 2022-12-14 10:48:00 +01:00
Willi Ballenthin
b819033da0 lots of mypy 2022-12-14 10:37:39 +01:00
Willi Ballenthin
35243ef7a6 changelog 2022-12-13 13:23:46 +00:00
Willi Ballenthin
655c45d43f Merge pull request #1226 from mandiant/dependabot/pip/pycodestyle-2.10.0
build(deps-dev): bump pycodestyle from 2.9.1 to 2.10.0
2022-12-13 14:15:58 +01:00
Willi Ballenthin
34c4809f68 Merge pull request #1228 from mandiant/dependabot/pip/pyinstaller-5.7.0
build(deps-dev): bump pyinstaller from 5.5 to 5.7.0
2022-12-13 14:15:46 +01:00
dependabot[bot]
f9b6800831 build(deps-dev): bump pycodestyle from 2.9.1 to 2.10.0
Bumps [pycodestyle](https://github.com/PyCQA/pycodestyle) from 2.9.1 to 2.10.0.
- [Release notes](https://github.com/PyCQA/pycodestyle/releases)
- [Changelog](https://github.com/PyCQA/pycodestyle/blob/main/CHANGES.txt)
- [Commits](https://github.com/PyCQA/pycodestyle/compare/2.9.1...2.10.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-13 13:15:33 +00:00
Willi Ballenthin
b5254e3662 Merge pull request #1227 from mandiant/dependabot/pip/mypy-0.991
build(deps-dev): bump mypy from 0.982 to 0.991
2022-12-13 14:15:07 +01:00
Willi Ballenthin
148cb71839 Merge pull request #1225 from mandiant/dependabot/pip/black-22.12.0
build(deps-dev): bump black from 22.10.0 to 22.12.0
2022-12-13 14:14:23 +01:00
Willi Ballenthin
62700ca5d1 setup: bump viv-utils to 0.7.7 for py3.11 support 2022-12-13 14:07:51 +01:00
Willi Ballenthin
b1d6fcd6c8 mypy 2022-12-13 13:20:24 +01:00
Willi Ballenthin
8afebc1f17 ci: mypy: enable --check-untyped-defs 2022-12-13 13:20:01 +01:00
Mike Hunhoff
447cd95bc5 ida: add support for COFF and extern functions (#1223) 2022-12-12 16:36:44 -07:00
Willi Ballenthin
5224380947 setup: viv-utils 0.7.6
closes #1192
2022-12-12 18:02:07 +01:00
Moritz
7aeb685412 Merge pull request #1224 from mandiant/williballenthin-patch-2
tests: os: fix test
2022-12-12 16:43:58 +01:00
Capa Bot
b6911f8ad2 Sync capa rules submodule 2022-12-12 14:39:26 +00:00
dependabot[bot]
a7d06275c1 build(deps-dev): bump pyinstaller from 5.5 to 5.7.0
Bumps [pyinstaller](https://github.com/pyinstaller/pyinstaller) from 5.5 to 5.7.0.
- [Release notes](https://github.com/pyinstaller/pyinstaller/releases)
- [Changelog](https://github.com/pyinstaller/pyinstaller/blob/develop/doc/CHANGES.rst)
- [Commits](https://github.com/pyinstaller/pyinstaller/compare/v5.5...v5.7.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-12 14:02:58 +00:00
dependabot[bot]
d581eefcdf build(deps-dev): bump mypy from 0.982 to 0.991
Bumps [mypy](https://github.com/python/mypy) from 0.982 to 0.991.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v0.982...v0.991)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-12 14:02:53 +00:00
dependabot[bot]
47f58162c5 build(deps-dev): bump black from 22.10.0 to 22.12.0
Bumps [black](https://github.com/psf/black) from 22.10.0 to 22.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/22.10.0...22.12.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-12 14:02:41 +00:00
Willi Ballenthin
ee72ed4b53 tests: os: fix test 2022-12-12 14:06:17 +01:00
Capa Bot
5cd7f33d00 Sync capa-testfiles submodule 2022-12-12 12:29:44 +00:00
Willi Ballenthin
d6674c7548 Merge pull request #1222 from mandiant/fix/issue-1221
elf: better detect linux ELF files
2022-12-12 13:28:59 +01:00
Capa Bot
a46d7b3262 Sync capa-testfiles submodule 2022-12-12 12:18:01 +00:00
Willi Ballenthin
0f902124d1 elf: reduce logging verbosity 2022-12-12 11:43:48 +01:00
Willi Ballenthin
d4a218e268 elf: os: bug fixes 2022-12-12 11:41:01 +01:00
Willi Ballenthin
22bef146f8 tests: add OS detection tests 2022-12-12 11:40:43 +01:00
Willi Ballenthin
b26ed47ab8 tests: add OS detection tests 2022-12-12 11:40:32 +01:00
Willi Ballenthin
7ba08edffa changelog 2022-12-09 16:09:41 +01:00
Willi Ballenthin
c958a6a286 elf: black 2022-12-09 16:07:46 +01:00
William Ballenthin
1583fedba2 mypy 2022-12-09 17:34:44 +01:00
William Ballenthin
307a6fad4f elf: os: detect via so dependencies 2022-12-09 14:31:03 +01:00
William Ballenthin
958d5bcc6a elf: refactor OS detection 2022-12-09 12:56:09 +01:00
William Ballenthin
c5a9aa21bf wip: elf: better detect linux ELF files 2022-12-08 21:33:57 +01:00
Willi Ballenthin
13b5d7c179 Merge pull request #1220 from mandiant/disable-smda-tests
skip smda tests until we remove the backend
2022-12-08 12:07:16 +01:00
Capa Bot
bd84ee83a5 Sync capa rules submodule 2022-12-07 19:10:53 +00:00
mr-tz
97f633312f skip smda tests until we remove the backend 2022-12-07 16:44:52 +01:00
Willi Ballenthin
b290690b19 Merge pull request #1216 from mandiant/fix/issue-1215
add missing vverbose feature renderers
2022-12-07 15:12:10 +01:00
Willi Ballenthin
fc57ed76a0 Merge pull request #1218 from mandiant/fix/issue-1194
small explorer fixes
2022-12-07 15:11:02 +01:00
Willi Ballenthin
a6fdb71178 utils: use a single hex() implementation 2022-12-07 14:09:37 +00:00
Willi Ballenthin
fe2f668306 CHANGELOG 2022-12-07 13:41:10 +00:00
Willi Ballenthin
45d007fa9a explorer: fix UnboundLocalError
closes #1217
2022-12-07 13:39:55 +00:00
Willi Ballenthin
662ec11031 explorer: accept only plaintext to rule window
closes #1194
2022-12-07 13:38:50 +00:00
Willi Ballenthin
1d8a3486cd vverbose: prefer isinstance checks over strings
which also makes mypy happier
2022-12-07 13:14:05 +00:00
Willi Ballenthin
c195afa0b3 explorer: improve rendering of operand number/offsets 2022-12-07 13:07:24 +00:00
Willi Ballenthin
63e0d9b3f3 vverbose: render offer and operand number/offset features
closes #1215
2022-12-07 12:59:37 +00:00
Willi Ballenthin
659cbedc3c vverbose: dont show offset for format 2022-12-07 12:59:21 +00:00
Willi Ballenthin
0ebba2cd15 vverbose: guard against rendering basic blocks 2022-12-07 12:58:55 +00:00
Willi Ballenthin
1f091a4ccd tests: add tests demonstrating vverbose feature rendering 2022-12-07 12:58:10 +00:00
Willi Ballenthin
d1aafa3764 vverbose: render offset
closes #1215
2022-12-07 11:52:41 +00:00
Willi Ballenthin
faefe41ad5 Merge pull request #1214 from mandiant/fix/pylint-fixes
pylint fixes
2022-12-07 12:41:57 +01:00
Willi Ballenthin
473d0daf58 render: pylint 2022-12-07 11:41:05 +00:00
Willi Ballenthin
a10abfebde main: pylint 2022-12-06 16:23:10 +00:00
Willi Ballenthin
78172b5f5b rules: pylint 2022-12-06 16:06:08 +00:00
Willi Ballenthin
1caeb248ca pylint: fix old-style super calls 2022-12-06 16:02:21 +00:00
Willi Ballenthin
8527d02dc8 pylint fixes 2022-12-06 15:37:31 +00:00
Willi Ballenthin
0e73f26e88 CHANGELOG 2022-12-06 15:34:22 +00:00
Willi Ballenthin
ed24db4460 extractors: remove SMDA backend
closes #1210
closes #1062
2022-12-06 15:33:17 +00:00
Willi Ballenthin
127886144b Merge pull request #1209 from mandiant/williballenthin-patch-3
import-to-ida: use other md5 function
2022-12-06 13:07:35 +01:00
Willi Ballenthin
c83877ec74 mypy: ignore ida_nalt 2022-12-06 12:06:07 +00:00
Willi Ballenthin
8d6fcd9939 Merge pull request #1208 from mandiant/williballenthin-patch-2
import-to-ida: fix append comment
2022-12-06 13:03:55 +01:00
Willi Ballenthin
1dc5e40308 Merge pull request #1206 from mandiant/dependabot/pip/termcolor-2.1.1
build(deps): bump termcolor from 2.0.1 to 2.1.1
2022-12-06 12:58:04 +01:00
Willi Ballenthin
cc832d26aa import-to-ida: fix imports 2022-12-05 15:27:22 +00:00
Willi Ballenthin
9fcb70387d import-to-ida: use other md5 function
ref #1204
2022-12-05 16:17:11 +01:00
Willi Ballenthin
236ad883d4 changelog 2022-12-05 15:13:16 +00:00
Willi Ballenthin
12c9c466c7 import-to-ida: fix append comment
ref #1204
2022-12-05 16:02:40 +01:00
dependabot[bot]
5a1cb0e48d build(deps): bump termcolor from 2.0.1 to 2.1.1
Bumps [termcolor](https://github.com/termcolor/termcolor) from 2.0.1 to 2.1.1.
- [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.0.1...2.1.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-28 14:03:10 +00:00
Capa Bot
5196caabb5 Sync capa rules submodule 2022-11-22 12:35:27 +00:00
Capa Bot
0f99592903 Sync capa-testfiles submodule 2022-11-08 19:58:11 +00:00
Capa Bot
56e9645700 Sync capa rules submodule 2022-10-24 18:28:08 +00:00
Capa Bot
0d8c6cc0fd Sync capa rules submodule 2022-10-13 14:37:09 +00:00
Mike Hunhoff
20c7949be3 dotnet: emit features from newobj instruction (#1186) 2022-10-13 08:35:29 -06:00
Willi Ballenthin
7cc6773bf8 Merge pull request #1185 from mandiant/dependabot/pip/pyinstaller-5.5
build(deps-dev): bump pyinstaller from 5.4.1 to 5.5
2022-10-11 15:56:11 +02:00
Willi Ballenthin
055700a5d1 Merge pull request #1182 from mandiant/dependabot/pip/mypy-0.982
build(deps-dev): bump mypy from 0.971 to 0.982
2022-10-11 15:55:37 +02:00
Willi Ballenthin
85b14075cd address: explicitly resolve hash from int 2022-10-11 09:47:25 +00:00
Willi Ballenthin
149c3989f1 Merge pull request #1178 from mandiant/dependabot/pip/pytest-cov-4.0.0
build(deps-dev): bump pytest-cov from 3.0.0 to 4.0.0
2022-10-11 10:58:49 +02:00
dependabot[bot]
3b5a34f331 build(deps-dev): bump mypy from 0.971 to 0.982
Bumps [mypy](https://github.com/python/mypy) from 0.971 to 0.982.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v0.971...v0.982)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-11 06:18:59 +00:00
dependabot[bot]
b4fe2d8592 build(deps-dev): bump pytest-cov from 3.0.0 to 4.0.0
Bumps [pytest-cov](https://github.com/pytest-dev/pytest-cov) from 3.0.0 to 4.0.0.
- [Release notes](https://github.com/pytest-dev/pytest-cov/releases)
- [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-cov/compare/v3.0.0...v4.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-11 06:18:47 +00:00
Moritz
67d06c73e0 Merge pull request #1183 from mandiant/dependabot/pip/types-tabulate-0.9.0.0
build(deps-dev): bump types-tabulate from 0.8.9 to 0.9.0.0
2022-10-11 08:18:21 +02:00
dependabot[bot]
81a942d7a1 build(deps-dev): bump pyinstaller from 5.4.1 to 5.5
Bumps [pyinstaller](https://github.com/pyinstaller/pyinstaller) from 5.4.1 to 5.5.
- [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.4.1...v5.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-11 06:18:13 +00:00
Moritz
521473cd81 Merge pull request #1184 from mandiant/dependabot/pip/black-22.10.0
build(deps-dev): bump black from 22.8.0 to 22.10.0
2022-10-11 08:18:02 +02:00
Moritz
676d422511 Merge pull request #1181 from mandiant/dependabot/pip/tabulate-0.9.0
build(deps): bump tabulate from 0.8.9 to 0.9.0
2022-10-11 08:17:45 +02:00
dependabot[bot]
f2dbb531fe build(deps-dev): bump black from 22.8.0 to 22.10.0
Bumps [black](https://github.com/psf/black) from 22.8.0 to 22.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/22.8.0...22.10.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-10 15:09:31 +00:00
dependabot[bot]
84fce86152 build(deps-dev): bump types-tabulate from 0.8.9 to 0.9.0.0
Bumps [types-tabulate](https://github.com/python/typeshed) from 0.8.9 to 0.9.0.0.
- [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-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-10 15:09:24 +00:00
dependabot[bot]
8307c66256 build(deps): bump tabulate from 0.8.9 to 0.9.0
Bumps [tabulate](https://github.com/astanin/python-tabulate) from 0.8.9 to 0.9.0.
- [Release notes](https://github.com/astanin/python-tabulate/releases)
- [Changelog](https://github.com/astanin/python-tabulate/blob/master/CHANGELOG)
- [Commits](https://github.com/astanin/python-tabulate/compare/v0.8.9...v0.9.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-10 15:09:17 +00:00
Capa Bot
ac71676d79 Sync capa rules submodule 2022-10-07 15:40:27 +00:00
Capa Bot
70e6d83259 Sync capa rules submodule 2022-10-03 15:28:44 +00:00
Capa Bot
3bbac4a35f Sync capa rules submodule 2022-10-03 15:17:03 +00:00
Capa Bot
87455ed6dd Sync capa-testfiles submodule 2022-09-20 19:34:29 +00:00
Mike Hunhoff
e1735f0a5e update pydantic models to guarantee type coercion (#1176)
* add CompoundStatement to fix Pydantic typing bug

* explorer: fix #1151

* explorer: support rendering operand number/offset
2022-09-20 08:38:19 -06:00
Capa Bot
8521f85742 Sync capa-testfiles submodule 2022-09-19 14:26:32 +00:00
Moritz
b1b15e2eef fix: do not overwrite __version__ (#1170) 2022-09-14 14:45:58 -06:00
Moritz
36e304839b Merge pull request #1173 from mandiant/dependabot/pip/pydantic-1.10.2
build(deps): bump pydantic from 1.10.1 to 1.10.2
2022-09-14 17:40:21 +02:00
Moritz
5a14a6d0cc Merge pull request #1172 from mandiant/dependabot/pip/termcolor-2.0.1
build(deps): bump termcolor from 1.1.0 to 2.0.1
2022-09-14 17:40:07 +02:00
Moritz
85901893a0 Merge pull request #1171 from mandiant/dependabot/pip/pyinstaller-5.4.1
build(deps-dev): bump pyinstaller from 5.3 to 5.4.1
2022-09-14 17:39:55 +02:00
dependabot[bot]
49d7f2a88f build(deps): bump pydantic from 1.10.1 to 1.10.2
Bumps [pydantic](https://github.com/pydantic/pydantic) from 1.10.1 to 1.10.2.
- [Release notes](https://github.com/pydantic/pydantic/releases)
- [Changelog](https://github.com/pydantic/pydantic/blob/main/HISTORY.md)
- [Commits](https://github.com/pydantic/pydantic/compare/v1.10.1...v1.10.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-12 14:17:04 +00:00
dependabot[bot]
8d8c5f99c1 build(deps): bump termcolor from 1.1.0 to 2.0.1
Bumps [termcolor](https://github.com/termcolor/termcolor) from 1.1.0 to 2.0.1.
- [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/1.1.0...2.0.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-12 14:17:00 +00:00
dependabot[bot]
4069515cad build(deps-dev): bump pyinstaller from 5.3 to 5.4.1
Bumps [pyinstaller](https://github.com/pyinstaller/pyinstaller) from 5.3 to 5.4.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.3...v5.4.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-12 14:16:54 +00:00
Mike Hunhoff
3c1cd67f60 dotnet: support property feature extraction (#1168) 2022-09-09 12:09:41 -06:00
Capa Bot
580948e46b Sync capa rules submodule 2022-09-09 11:21:24 +00:00
Capa Bot
4ffd7b89f3 Sync capa rules submodule 2022-09-09 11:19:59 +00:00
Moritz
2441c18a85 fix: use int instead of Token to decouple extractor and features (#1158) 2022-09-08 11:09:17 -06:00
Moritz
ee89fa45b6 Update build.yml (#1157) 2022-09-08 10:58:29 -06:00
Moritz
3976e5858d feat: verify rule metadata format on load (#1160) 2022-09-08 10:56:59 -06:00
Capa Bot
4e542f9cff Sync capa rules submodule 2022-09-08 08:42:53 +00:00
Moritz
ce1ecfad4d Merge pull request #1164 from mandiant/dependabot/pip/psutil-5.9.2
build(deps-dev): bump psutil from 5.9.1 to 5.9.2
2022-09-06 17:40:59 +02:00
dependabot[bot]
d9d5aaffa1 build(deps-dev): bump psutil from 5.9.1 to 5.9.2
Bumps [psutil](https://github.com/giampaolo/psutil) from 5.9.1 to 5.9.2.
- [Release notes](https://github.com/giampaolo/psutil/releases)
- [Changelog](https://github.com/giampaolo/psutil/blob/master/HISTORY.rst)
- [Commits](https://github.com/giampaolo/psutil/compare/release-5.9.1...release-5.9.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-06 06:53:53 +00:00
Moritz
21809350f7 Merge pull request #1166 from mandiant/dependabot/pip/pydantic-1.10.1
build(deps): bump pydantic from 1.9.2 to 1.10.1
2022-09-06 08:53:46 +02:00
Moritz
418b063067 Merge pull request #1165 from mandiant/dependabot/pip/tqdm-4.64.1
build(deps): bump tqdm from 4.64.0 to 4.64.1
2022-09-06 08:53:30 +02:00
Moritz
dcf838872c Merge pull request #1163 from mandiant/dependabot/pip/pytest-7.1.3
build(deps-dev): bump pytest from 7.1.2 to 7.1.3
2022-09-06 08:53:07 +02:00
Moritz
456b32e6a8 Merge pull request #1162 from mandiant/dependabot/pip/black-22.8.0
build(deps-dev): bump black from 22.6.0 to 22.8.0
2022-09-06 08:52:51 +02:00
dependabot[bot]
acad9c5570 build(deps): bump pydantic from 1.9.2 to 1.10.1
Bumps [pydantic](https://github.com/pydantic/pydantic) from 1.9.2 to 1.10.1.
- [Release notes](https://github.com/pydantic/pydantic/releases)
- [Changelog](https://github.com/pydantic/pydantic/blob/main/HISTORY.md)
- [Commits](https://github.com/pydantic/pydantic/compare/v1.9.2...v1.10.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-05 16:32:53 +00:00
dependabot[bot]
4b2cfb4825 build(deps): bump tqdm from 4.64.0 to 4.64.1
Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.64.0 to 4.64.1.
- [Release notes](https://github.com/tqdm/tqdm/releases)
- [Commits](https://github.com/tqdm/tqdm/compare/v4.64.0...v4.64.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-05 16:32:49 +00:00
dependabot[bot]
7733562587 build(deps-dev): bump pytest from 7.1.2 to 7.1.3
Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.1.2 to 7.1.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.1.2...7.1.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-05 16:32:34 +00:00
dependabot[bot]
eaa70fa80f build(deps-dev): bump black from 22.6.0 to 22.8.0
Bumps [black](https://github.com/psf/black) from 22.6.0 to 22.8.0.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/22.6.0...22.8.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-05 16:32:26 +00:00
Capa Bot
44843ea977 Sync capa rules submodule 2022-08-29 16:50:20 +00:00
Capa Bot
cac041b869 Sync capa-testfiles submodule 2022-08-24 10:47:31 +00:00
Moritz
49684e4c25 fix: display instruction items (#1155)
* fix: display instruction items

* fix: instruction item format
2022-08-23 17:12:51 +02:00
Mike Hunhoff
47268c2344 render: convert feature attributes to aliased dictionary for vverbose (#1152) 2022-08-18 12:15:52 -06:00
Moritz
da0a1e7903 Merge pull request #1149 from gdesmar/master
Fix maec.malware_category_ov typo in vverbose render
2022-08-18 11:31:40 +02:00
Moritz
eca1582678 Merge pull request #1148 from idiom/master
Add Optional attribute to argv property in Metadata model.
2022-08-18 11:31:23 +02:00
gdesmar
2049058b45 render: vverbose, fix maec.malware_category_ov typo 2022-08-16 18:40:51 +00:00
Moritz
c2b5e7116d Merge pull request #1146 from mandiant/dependabot/pip/dnfile-0.12.0
build(deps): bump dnfile from 0.11.0 to 0.12.0
2022-08-16 11:06:15 +02:00
dependabot[bot]
9c1b076a5f build(deps): bump dnfile from 0.11.0 to 0.12.0
Bumps [dnfile](https://github.com/malwarefrank/dnfile) from 0.11.0 to 0.12.0.
- [Release notes](https://github.com/malwarefrank/dnfile/releases)
- [Changelog](https://github.com/malwarefrank/dnfile/blob/master/HISTORY.rst)
- [Commits](https://github.com/malwarefrank/dnfile/compare/v0.11.0...v0.12.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-16 08:14:55 +00:00
Moritz
51f7e10cb6 Merge pull request #1145 from mandiant/dependabot/pip/pyelftools-0.29
build(deps): bump pyelftools from 0.28 to 0.29
2022-08-16 10:14:24 +02:00
Moritz
25ad6446ba Merge pull request #1144 from mandiant/dependabot/pip/pydantic-1.9.2
build(deps): bump pydantic from 1.9.1 to 1.9.2
2022-08-16 10:14:04 +02:00
idiom
1af5255501 Add Optional attribute to argv property in Metadata model. This resovles issue where a ValidationError is raised when argv is not in the passed matedata and set to None in from_capa. 2022-08-15 15:55:19 -04:00
dependabot[bot]
49d61db8f9 build(deps): bump pyelftools from 0.28 to 0.29
Bumps [pyelftools](https://github.com/eliben/pyelftools) from 0.28 to 0.29.
- [Release notes](https://github.com/eliben/pyelftools/releases)
- [Changelog](https://github.com/eliben/pyelftools/blob/master/CHANGES)
- [Commits](https://github.com/eliben/pyelftools/compare/v0.28...v0.29)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-15 14:23:21 +00:00
dependabot[bot]
601471c1e6 build(deps): bump pydantic from 1.9.1 to 1.9.2
Bumps [pydantic](https://github.com/samuelcolvin/pydantic) from 1.9.1 to 1.9.2.
- [Release notes](https://github.com/samuelcolvin/pydantic/releases)
- [Changelog](https://github.com/pydantic/pydantic/blob/master/HISTORY.md)
- [Commits](https://github.com/samuelcolvin/pydantic/compare/v1.9.1...v1.9.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-15 14:23:17 +00:00
Moritz
3c4141589d Release v4.0.1 (#1143)
* update scripts/lint.py to validate rule metadata using pydantic (#1141)

* doc: v401

Co-authored-by: Mike Hunhoff <mike.hunhoff@gmail.com>
2022-08-15 13:26:39 +02:00
Moritz
c5f768accc Update document and version set in PyInstaller (#1140)
* doc: update v4 resources

* tmp

* fix: set version #1136

* format: black

* comment version substring replacement
2022-08-15 11:10:17 +02:00
Capa Bot
2e6671ff91 Sync capa rules submodule 2022-08-15 08:39:57 +00:00
Capa Bot
f4171c32cf Sync capa-testfiles submodule 2022-08-15 08:31:20 +00:00
Mike Hunhoff
449c64d80b update scripts/lint.py to validate rule metadata using pydantic (#1141) 2022-08-12 08:26:39 -06:00
Capa Bot
735cb57b10 Sync capa rules submodule 2022-08-12 09:29:53 +00:00
Moritz
81cb4b31e1 Release v4.0.0 (#1105)
* release: v4 prep

* add SMDA deprecation warning

* doc: update v4 changes

* Update CHANGELOG.md

* Update CHANGELOG.md

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

* doc: add DeprecationWarning

* fix: add __index__ method

* ci: test build run on more OSs

* explorer: update supported versions to include IDA 8.0

Co-authored-by: Mike Hunhoff <mike.hunhoff@gmail.com>
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2022-08-10 15:32:52 +02:00
Capa Bot
e564466ac8 Sync capa rules submodule 2022-08-09 10:50:59 +00:00
Capa Bot
63e0d903c7 Sync capa rules submodule 2022-08-09 10:35:39 +00:00
Moritz
dbc1ddcd7b Merge pull request #1133 from mandiant/dependabot/pip/smda-1.8.4
build(deps): bump smda from 1.7.4 to 1.8.4
2022-08-09 12:34:32 +02:00
Moritz
a00d0d5222 Merge pull request #1128 from mandiant/dependabot/pip/pyinstaller-5.3
build(deps-dev): bump pyinstaller from 5.2 to 5.3
2022-08-09 12:33:59 +02:00
Moritz
428d125340 Merge pull request #1132 from mandiant/dependabot/pip/pycodestyle-2.9.1
build(deps-dev): bump pycodestyle from 2.8.0 to 2.9.1
2022-08-09 12:32:19 +02:00
dependabot[bot]
f94314d8ec build(deps): bump smda from 1.7.4 to 1.8.4
Bumps [smda](https://github.com/danielplohmann/smda) from 1.7.4 to 1.8.4.
- [Release notes](https://github.com/danielplohmann/smda/releases)
- [Commits](https://github.com/danielplohmann/smda/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-08 14:11:12 +00:00
dependabot[bot]
bb94ca3b18 build(deps-dev): bump pycodestyle from 2.8.0 to 2.9.1
Bumps [pycodestyle](https://github.com/PyCQA/pycodestyle) from 2.8.0 to 2.9.1.
- [Release notes](https://github.com/PyCQA/pycodestyle/releases)
- [Changelog](https://github.com/PyCQA/pycodestyle/blob/main/CHANGES.txt)
- [Commits](https://github.com/PyCQA/pycodestyle/compare/2.8.0...2.9.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-08 14:11:11 +00:00
Capa Bot
5823d421fd Sync capa-testfiles submodule 2022-08-01 20:50:09 +00:00
Capa Bot
045a64496e Sync capa-testfiles submodule 2022-08-01 20:36:11 +00:00
Capa Bot
b8905e3e48 Sync capa-testfiles submodule 2022-08-01 20:35:55 +00:00
Capa Bot
7c6f27c6d7 Sync capa-testfiles submodule 2022-08-01 20:35:14 +00:00
Capa Bot
995b144f0b Sync capa-testfiles submodule 2022-08-01 20:34:46 +00:00
Capa Bot
ba93803d3f Sync capa-testfiles submodule 2022-08-01 20:30:55 +00:00
dependabot[bot]
96b13907e2 build(deps-dev): bump pyinstaller from 5.2 to 5.3
Bumps [pyinstaller](https://github.com/pyinstaller/pyinstaller) from 5.2 to 5.3.
- [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.2...v5.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-01 14:14:42 +00:00
Willi Ballenthin
2f7aa14f61 Merge pull request #1118 from mandiant/dependabot/pip/dncil-1.0.1
build(deps): bump dncil from 1.0.0 to 1.0.1
2022-07-25 10:57:12 -06:00
Willi Ballenthin
f93b94f073 Merge pull request #1119 from mandiant/dependabot/pip/smda-1.7.4
build(deps): bump smda from 1.7.2 to 1.7.4
2022-07-25 10:56:54 -06:00
Willi Ballenthin
30835b5ce4 Merge pull request #1120 from mandiant/dependabot/pip/mypy-0.971
build(deps-dev): bump mypy from 0.961 to 0.971
2022-07-25 10:56:19 -06:00
dependabot[bot]
98db89e45a build(deps-dev): bump mypy from 0.961 to 0.971
Bumps [mypy](https://github.com/python/mypy) from 0.961 to 0.971.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v0.961...v0.971)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-25 14:09:17 +00:00
dependabot[bot]
84c4b3ca8f build(deps): bump smda from 1.7.2 to 1.7.4
Bumps [smda](https://github.com/danielplohmann/smda) from 1.7.2 to 1.7.4.
- [Release notes](https://github.com/danielplohmann/smda/releases)
- [Commits](https://github.com/danielplohmann/smda/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-25 14:09:11 +00:00
dependabot[bot]
cd32abc405 build(deps): bump dncil from 1.0.0 to 1.0.1
Bumps [dncil](https://github.com/mandiant/dncil) from 1.0.0 to 1.0.1.
- [Release notes](https://github.com/mandiant/dncil/releases)
- [Commits](https://github.com/mandiant/dncil/compare/v1.0.0...v1.0.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-25 14:09:10 +00:00
dependabot[bot]
bae1b29505 build(deps-dev): bump types-colorama from 0.4.14 to 0.4.15 (#1113)
Bumps [types-colorama](https://github.com/python/typeshed) from 0.4.14 to 0.4.15.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-colorama
  dependency-type: direct:development
  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>
2022-07-19 20:46:20 +02:00
dependabot[bot]
5061a0c717 build(deps-dev): bump types-requests from 2.28.0 to 2.28.1 (#1112)
Bumps [types-requests](https://github.com/python/typeshed) from 2.28.0 to 2.28.1.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-requests
  dependency-type: direct:development
  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>
2022-07-19 20:45:53 +02:00
Capa Bot
404de45103 Sync capa rules submodule 2022-07-12 16:10:32 +00:00
Willi Ballenthin
39c8674da5 Merge pull request #1103 from mandiant/dependabot/pip/pyinstaller-5.2
build(deps-dev): bump pyinstaller from 5.1 to 5.2
2022-07-11 08:57:24 -06:00
Willi Ballenthin
954b90befb Merge pull request #1101 from mandiant/dependabot/pip/smda-1.7.2
build(deps): bump smda from 1.7.1 to 1.7.2
2022-07-11 08:56:18 -06:00
dependabot[bot]
62422ae4d9 build(deps-dev): bump pyinstaller from 5.1 to 5.2
Bumps [pyinstaller](https://github.com/pyinstaller/pyinstaller) from 5.1 to 5.2.
- [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.1...v5.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-11 14:26:33 +00:00
dependabot[bot]
6594d9d911 build(deps): bump smda from 1.7.1 to 1.7.2
Bumps [smda](https://github.com/danielplohmann/smda) from 1.7.1 to 1.7.2.
- [Release notes](https://github.com/danielplohmann/smda/releases)
- [Commits](https://github.com/danielplohmann/smda/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-11 14:26:26 +00:00
Anushka Virgaonkar
6e9676e0be fix #1095 (#1098) 2022-07-08 15:41:42 -06:00
Capa Bot
6764830f2d Sync capa-testfiles submodule 2022-07-08 18:59:42 +00:00
Mike Hunhoff
747eed4db7 render: display number feature as hex vverbose (#1097) 2022-07-08 10:37:41 -06:00
Capa Bot
28f32eebfc Sync capa rules submodule 2022-07-07 21:24:45 +00:00
Capa Bot
3dbd57ffe4 Sync capa rules submodule 2022-07-07 21:23:20 +00:00
Capa Bot
e63a9c801b Sync capa rules submodule 2022-07-07 19:40:55 +00:00
Capa Bot
0fbea75513 Sync capa rules submodule 2022-07-07 17:38:22 +00:00
Mike Hunhoff
4b3129e30a ida: bug fixes including #1090 for Address abstraction (#1091) 2022-07-07 09:54:20 -06:00
Anushka Virgaonkar
10c16e8a71 fix #1089: add feature format to global scope (#1093) 2022-07-07 08:15:34 +02:00
Capa Bot
21efdd2e0e Sync capa rules submodule 2022-07-06 19:36:29 +00:00
dependabot[bot]
ac1add3fcb build(deps-dev): bump black from 22.3.0 to 22.6.0 (#1085)
Bumps [black](https://github.com/psf/black) from 22.3.0 to 22.6.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/22.3.0...22.6.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2022-07-06 10:11:31 +02:00
Moritz
b4d2fecf4b fix: string extraction and rendering (#1083) 2022-07-05 13:17:24 -06:00
Capa Bot
ec81768fb5 Sync capa rules submodule 2022-07-05 15:37:24 +00:00
Capa Bot
0f60165135 Sync capa rules submodule 2022-07-05 07:53:28 +00:00
Capa Bot
7c54502dc8 Sync capa rules submodule 2022-07-05 07:17:51 +00:00
Moritz
38668b2c4a fix: use DnfileFeatureExtractor (#1088)
closes #1087
2022-07-05 07:53:47 +02:00
Moritz
d210645aee fix: api handling workaround (#1079)
* fix: api handling workaround

* fix: workaround .NET api handling
2022-07-01 17:37:56 +02:00
Capa Bot
444c30d720 Sync capa rules submodule 2022-07-01 14:50:27 +00:00
Willi Ballenthin
22bc26905f Merge pull request #1082 from mandiant/fix/addr-check
check address value
2022-07-01 08:49:17 -06:00
Moritz Raabe
9f4479582a check address value
closes #1081
2022-07-01 15:40:23 +02:00
Capa Bot
7bd49b56c4 Sync capa rules submodule 2022-06-30 15:17:33 +00:00
Capa Bot
9015761d4d Sync capa-testfiles submodule 2022-06-30 15:16:42 +00:00
Capa Bot
36eabc1c39 Sync capa rules submodule 2022-06-29 23:22:05 +00:00
Capa Bot
2f792427f9 Sync capa rules submodule 2022-06-29 17:55:13 +00:00
Capa Bot
cc06101cdc Sync capa rules submodule 2022-06-29 17:54:42 +00:00
Capa Bot
7387c56af9 Sync capa-testfiles submodule 2022-06-29 17:47:36 +00:00
Mike Hunhoff
998364d500 update documentation for .NET (#1074) 2022-06-29 08:22:48 -06:00
Willi Ballenthin
e7cf69a82e pep8 2022-06-28 15:58:02 -06:00
Willi Ballenthin
8dbb5a097c Merge branch 'master' of github.com:mandiant/capa 2022-06-28 15:54:19 -06:00
Willi Ballenthin
91818a116d scripts/capa_as_library: use new ResultDocument
closes #1071
2022-06-28 15:53:37 -06:00
Capa Bot
82e8f8f090 Sync capa rules submodule 2022-06-28 21:34:02 +00:00
Willi Ballenthin
2a0ada9848 Merge pull request #1072 from mandiant/revert-1014-fix/temp-accept-x32-x64-but-ignore
Revert "fix: temporarily accept x32/x64 flavors but ignore"
2022-06-28 14:28:08 -06:00
Willi Ballenthin
b87b03300a Revert "fix: temporarily accept x32/x64 flavors but ignore (#1014)"
This reverts commit daf483309e.
2022-06-28 14:27:48 -06:00
Capa Bot
ecd88680dd Sync capa rules submodule 2022-06-28 20:26:53 +00:00
Capa Bot
45c39cfd7a Sync capa rules submodule 2022-06-28 20:11:57 +00:00
Capa Bot
46ad23fb30 Sync capa rules submodule 2022-06-28 20:04:59 +00:00
Capa Bot
0e6a050921 Sync capa rules submodule 2022-06-28 20:04:14 +00:00
Moritz
f72f8b054a ci: use macos-11 (#1066)
* ci: use macos-11

* ci: use macos-10.15 for build
2022-06-28 21:47:06 +02:00
Willi Ballenthin
1d61b24eb0 Merge pull request #1055 from mandiant/dependabot/pip/types-requests-2.28.0
build(deps-dev): bump types-requests from 2.27.30 to 2.28.0
2022-06-28 11:35:09 -06:00
Willi Ballenthin
5a73a8d7bb Merge pull request #1069 from mandiant/fix-1053
render: verbose: render metadata tokens correctly
2022-06-28 11:30:48 -06:00
Willi Ballenthin
b2507d14c0 Merge pull request #1070 from mandiant/williballenthin-patch-1
changelog: document upcoming deprecation of SMDA backend
2022-06-28 11:30:10 -06:00
Willi Ballenthin
b6f932ea15 changelog: document upcoming deprecation of SMDA backend
closes #1061
2022-06-28 11:24:31 -06:00
Willi Ballenthin
bb1afb3356 render: verbose: render metadata tokens correctly
closes #1053
2022-06-28 11:18:24 -06:00
Capa Bot
d35ac32f0a Sync capa rules submodule 2022-06-28 17:13:42 +00:00
Willi Ballenthin
cb6781a143 verbose: remove extraneous import 2022-06-28 11:11:00 -06:00
Willi Ballenthin
e7fa1ae52c Merge pull request #1068 from mandiant/feat-lint-dotnet-filename
linter: expect file path "dotnet" for ".NET" rules
2022-06-28 11:05:37 -06:00
Willi Ballenthin
8b7ddc5679 linter: expect file path "dotnet" for ".NET" rules
ref: https://github.com/mandiant/capa-rules/pull/568#discussion_r908718249
2022-06-28 10:48:03 -06:00
Willi Ballenthin
3323d85067 Merge pull request #1067 from mandiant/fix-966
main: suppress low level ELF parsing log messages
2022-06-28 10:46:32 -06:00
Willi Ballenthin
9019e6b0f5 main: suppress low level ELF parsing log messages
closes #966
2022-06-28 10:45:38 -06:00
Capa Bot
c6c2fc9f2a Sync capa rules submodule 2022-06-28 16:44:02 +00:00
Willi Ballenthin
6ea15901d6 Merge pull request #1064 from mandiant/mr-tz-patch-1
Update README.md
2022-06-28 10:42:44 -06:00
Willi Ballenthin
400e28c3f7 document rule branches (#1006)
* doc: add rules documentation

* version: add accessor for capa major version and git checkout command

* doc: rules: document version output

* doc: rules: show example error due to mismatched rule version

* doc: rules: update links

* ci: tag major rule version test

* ci: tag major rule version

* doc: update changelog

* ci: force to replace tag if exists

* ci: simplify tagging step

* Update doc/rules.md

* feat: disable tag, show error branch cmd

* doc: update major version rules branch

* Apply suggestions from code review

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

* Update capa/main.py

* black

Co-authored-by: Moritz Raabe <moritz.raabe@mandiant.com>
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2022-06-28 18:38:51 +02:00
Moritz
f2281b8e6e Update README.md 2022-06-28 18:33:42 +02:00
Willi Ballenthin
ad88e51228 Merge pull request #1060 from mandiant/feat/frz-extractor-info
Feat/frz extractor info
2022-06-28 10:32:37 -06:00
Capa Bot
2b17b22d33 Sync capa rules submodule 2022-06-28 16:30:17 +00:00
Willi Ballenthin
da6f6dd94f Merge pull request #1063 from mandiant/fix-blog-links
readme: fix blog links
2022-06-28 10:30:08 -06:00
Willi Ballenthin
09d444222a readme: fix blog links
closes #800
2022-06-28 10:27:31 -06:00
Capa Bot
a5c9993b61 Sync capa rules submodule 2022-06-28 14:06:49 +00:00
Moritz
f03eb87892 Merge pull request #1059 from mandiant/fix/linter-refs-authors
feat: lint check value types
2022-06-28 15:57:39 +02:00
Moritz Raabe
a7c4761fef isort, black 2022-06-28 15:53:10 +02:00
Moritz Raabe
e2156c3854 refactor: parametrize test 2022-06-28 15:49:21 +02:00
Moritz Raabe
bf53958887 fix!: global feat frz address 2022-06-28 15:47:12 +02:00
Moritz Raabe
e4d532e212 feat!: add extractor info to frz format 2022-06-28 15:43:59 +02:00
Moritz Raabe
9bf582a89a feat: lint check value types
closes #1027
2022-06-28 12:53:31 +02:00
dependabot[bot]
470995a541 build(deps-dev): bump types-requests from 2.27.30 to 2.28.0
Bumps [types-requests](https://github.com/python/typeshed) from 2.27.30 to 2.28.0.
- [Release notes](https://github.com/python/typeshed/releases)
- [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>
2022-06-27 14:10:51 +00:00
Capa Bot
79ce903817 Sync capa rules submodule 2022-06-23 13:27:54 +00:00
Willi Ballenthin
6fa8f9e401 Merge pull request #1052 from mandiant/dependabot/pip/colorama-0.4.5
build(deps): bump colorama from 0.4.4 to 0.4.5
2022-06-21 09:45:21 -06:00
Willi Ballenthin
fb99ef56e3 Merge pull request #986 from mandiant/feature-981
add Address abstraction
2022-06-21 09:44:57 -06:00
Willi Ballenthin
be2dffe863 bulk-process: use new ResultDocument json 2022-06-20 14:43:30 -06:00
Willi Ballenthin
e3804a0596 main: add types for collect_metadata 2022-06-20 14:43:18 -06:00
Willi Ballenthin
9ebea05933 show-capabilities-by-function: use new ResultDocument 2022-06-20 14:32:10 -06:00
Willi Ballenthin
a453258a51 tests: fix render test for MBC 2022-06-20 14:25:18 -06:00
Willi Ballenthin
246ef58e7b tests: fix render test for ATT&CK metadata 2022-06-20 14:24:01 -06:00
dependabot[bot]
d55d1facd5 build(deps): bump colorama from 0.4.4 to 0.4.5
Bumps [colorama](https://github.com/tartley/colorama) from 0.4.4 to 0.4.5.
- [Release notes](https://github.com/tartley/colorama/releases)
- [Changelog](https://github.com/tartley/colorama/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/tartley/colorama/compare/0.4.4...0.4.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-20 14:20:36 +00:00
William Ballenthin
a5979d3b4d Merge branch 'feature-981' of github.com:fireeye/capa into feature-981 2022-06-14 17:43:24 -06:00
William Ballenthin
af9049da6e dnfile: return NO_ADDRESS for base_address 2022-06-14 17:43:04 -06:00
William Ballenthin
6b5e125592 extractors: mypy 2022-06-14 17:42:04 -06:00
William Ballenthin
ee5c86913d extractor: clarify base address handling 2022-06-14 17:40:04 -06:00
Willi Ballenthin
0ff3bf1e5e Update .github/workflows/tests.yml 2022-06-14 17:07:25 -06:00
Willi Ballenthin
f5b79c0285 Update .github/workflows/tests.yml 2022-06-14 17:06:48 -06:00
William Ballenthin
c417b5dd79 merge master 2022-06-14 17:05:46 -06:00
William Ballenthin
bb74c73f6f sync rules 2022-06-14 17:02:34 -06:00
Willi Ballenthin
df101e5a60 Update capa/features/extractors/dnfile/extractor.py
Co-authored-by: Mike Hunhoff <mike.hunhoff@gmail.com>
2022-06-14 17:01:20 -06:00
William Ballenthin
aff6191b11 ida: meta: provide [] as argv 2022-06-14 16:59:58 -06:00
William Ballenthin
269f056e52 ida: use new ResultDocument structures 2022-06-14 16:58:32 -06:00
William Ballenthin
9c77488937 ida: meta: extract os/format/platform 2022-06-14 16:57:19 -06:00
Moritz
2ceed78924 Merge pull request #1050 from mandiant/dependabot/pip/mypy-0.961
build(deps-dev): bump mypy from 0.960 to 0.961
2022-06-13 19:24:37 +02:00
Moritz
df99b1d394 Merge pull request #1049 from mandiant/dependabot/pip/requests-2.28.0
build(deps-dev): bump requests from 2.27.1 to 2.28.0
2022-06-13 19:24:29 +02:00
dependabot[bot]
57633ceeb2 build(deps-dev): bump mypy from 0.960 to 0.961
Bumps [mypy](https://github.com/python/mypy) from 0.960 to 0.961.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v0.960...v0.961)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-13 14:16:18 +00:00
dependabot[bot]
7aa041c4d1 build(deps-dev): bump requests from 2.27.1 to 2.28.0
Bumps [requests](https://github.com/psf/requests) from 2.27.1 to 2.28.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.27.1...v2.28.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-13 14:16:11 +00:00
Willi Ballenthin
8031be75ab render: fix computation of subrule matches 2022-06-10 15:06:22 -06:00
Willi Ballenthin
3103307601 tests: fix reference error 2022-06-10 14:58:26 -06:00
Willi Ballenthin
6568189839 freeze: fix sorting of addresses 2022-06-10 14:58:17 -06:00
Capa Bot
c653dd7e72 Sync capa-testfiles submodule 2022-06-10 20:48:49 +00:00
Willi Ballenthin
1c771da848 pep8 2022-06-10 14:47:23 -06:00
Willi Ballenthin
5b5ac16830 render: fix rendering of .NET tokens 2022-06-10 14:47:06 -06:00
Willi Ballenthin
67221e5907 freeze: fix (de)serialization of tokens 2022-06-10 14:46:56 -06:00
Willi Ballenthin
6a5271c16f remove old file 2022-06-10 14:38:14 -06:00
William Ballenthin
c3418fddb5 tests: json: fix address representation 2022-06-08 13:29:04 -06:00
William Ballenthin
faf414e3d8 tests: add more dotnet tests 2022-06-08 13:28:53 -06:00
William Ballenthin
c6144a1dfa freeze: address: fix .NET address sorting 2022-06-08 13:07:10 -06:00
William Ballenthin
ad153499a3 address: implement __eq__ 2022-06-08 12:46:50 -06:00
William Ballenthin
2767660722 features: substring: correctly record no captures 2022-06-08 11:09:53 -06:00
Willi Ballenthin
9433d41588 Update capa/features/address.py
Co-authored-by: Mike Hunhoff <mike.hunhoff@gmail.com>
2022-06-08 10:10:20 -06:00
Willi Ballenthin
96b522cf6c Update capa/features/address.py
Co-authored-by: Mike Hunhoff <mike.hunhoff@gmail.com>
2022-06-08 10:10:12 -06:00
Willi Ballenthin
f35a82562b Update capa/features/extractors/dnfile/insn.py
Co-authored-by: Mike Hunhoff <mike.hunhoff@gmail.com>
2022-06-08 10:00:21 -06:00
Willi Ballenthin
bfda997fdf freeze: support Class and Namespace features, too 2022-06-06 15:55:12 -06:00
Willi Ballenthin
9c09923b86 main: fix .NET format detection 2022-06-06 15:46:40 -06:00
Willi Ballenthin
3ef126fbd7 show-features: fix rendering addresses 2022-06-06 15:27:06 -06:00
Willi Ballenthin
9fdaa91fa9 render: vverbose: fixup rendering of imports 2022-06-06 15:22:06 -06:00
Willi Ballenthin
0987141970 tests: add tests demonstrating rending of .NET samples 2022-06-06 15:13:20 -06:00
Willi Ballenthin
c73db051c1 fixtures: add path to extractors 2022-06-06 15:13:11 -06:00
Willi Ballenthin
9a8d28d107 viv: remove old handle implementation 2022-06-06 15:05:24 -06:00
Willi Ballenthin
0b11a35358 Merge pull request #1045 from mandiant/dependabot/pip/types-requests-2.27.30
build(deps-dev): bump types-requests from 2.27.29 to 2.27.30
2022-06-06 15:00:48 -06:00
dependabot[bot]
524ab86d24 build(deps-dev): bump types-requests from 2.27.29 to 2.27.30
Bumps [types-requests](https://github.com/python/typeshed) from 2.27.29 to 2.27.30.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-06 21:00:10 +00:00
Willi Ballenthin
0060daf2e8 Merge pull request #1046 from mandiant/dependabot/pip/types-psutil-5.8.23
build(deps-dev): bump types-psutil from 5.8.22 to 5.8.23
2022-06-06 14:59:18 -06:00
Willi Ballenthin
f5eb52f7c9 Merge pull request #1048 from mandiant/dependabot/pip/types-pyyaml-6.0.8
build(deps-dev): bump types-pyyaml from 6.0.7 to 6.0.8
2022-06-06 14:59:01 -06:00
Willi Ballenthin
59944d6aa6 Merge pull request #1039 from mandiant/dependabot/pip/pefile-2022.5.30
build(deps): bump pefile from 2021.9.3 to 2022.5.30
2022-06-06 14:58:52 -06:00
Willi Ballenthin
a6a48dc7a3 Merge pull request #1047 from mandiant/dependabot/pip/viv-utils-flirt--0.7.5
build(deps): bump viv-utils[flirt] from 0.7.4 to 0.7.5
2022-06-06 14:58:43 -06:00
Willi Ballenthin
1b951aa2d5 *: remove unused imports 2022-06-06 14:33:34 -06:00
Willi Ballenthin
a66c6c9d23 setup: fix pydantic dep version 2022-06-06 14:29:22 -06:00
Willi Ballenthin
dddcec4be3 setup: fix dep spec 2022-06-06 14:27:34 -06:00
Willi Ballenthin
1a290a38c4 Merge branch 'master' into feature-981 2022-06-06 14:07:51 -06:00
Willi Ballenthin
dcdc70de49 Merge branch 'feature-981' of github.com:mandiant/capa into feature-981 2022-06-06 13:49:28 -06:00
Willi Ballenthin
f8b10a2c0a render: verbose: update to use new result document 2022-06-06 13:48:41 -06:00
Willi Ballenthin
5960f51f13 result document: fix type of statement node 2022-06-06 13:47:39 -06:00
Willi Ballenthin
59e0518e6d pep8/mypy 2022-06-06 13:47:17 -06:00
Willi Ballenthin
afc2953538 frz: address: make sortable 2022-06-06 13:45:13 -06:00
Willi Ballenthin
f58966acf8 address: implement repr, not str 2022-06-06 13:44:59 -06:00
Willi Ballenthin
cb44704d38 features: bb: add description to BasicBlock feature 2022-06-06 13:44:40 -06:00
Willi Ballenthin
ab4177fae1 render: default: fix rendering of mbc/att&ck 2022-06-06 13:44:08 -06:00
Willi Ballenthin
867662ba5a rules: remove unused rule-category meta 2022-06-06 13:43:44 -06:00
Capa Bot
6cb4493b8e Sync capa rules submodule 2022-06-06 19:18:31 +00:00
dependabot[bot]
0444ab0bc5 build(deps-dev): bump types-pyyaml from 6.0.7 to 6.0.8
Bumps [types-pyyaml](https://github.com/python/typeshed) from 6.0.7 to 6.0.8.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-06 14:11:09 +00:00
dependabot[bot]
51a2da7e05 build(deps): bump viv-utils[flirt] from 0.7.4 to 0.7.5
Bumps [viv-utils[flirt]](https://github.com/williballenthin/viv-utils) from 0.7.4 to 0.7.5.
- [Release notes](https://github.com/williballenthin/viv-utils/releases)
- [Commits](https://github.com/williballenthin/viv-utils/compare/v0.7.4...v0.7.5)

---
updated-dependencies:
- dependency-name: viv-utils[flirt]
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-06 14:11:08 +00:00
dependabot[bot]
d625e99dd0 build(deps-dev): bump types-psutil from 5.8.22 to 5.8.23
Bumps [types-psutil](https://github.com/python/typeshed) from 5.8.22 to 5.8.23.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-06 14:11:03 +00:00
Capa Bot
43dca13f26 Sync capa rules submodule 2022-06-03 17:16:28 +00:00
Capa Bot
bc8c4a0323 Sync capa rules submodule 2022-06-01 12:13:22 +00:00
Moritz
d8e68255a0 Merge pull request #1044 from mandiant/fix/rules-meta-authors
fix!: authors instead of author
2022-06-01 14:12:31 +02:00
Capa Bot
781ec74310 Sync capa-testfiles submodule 2022-06-01 12:12:01 +00:00
Moritz Raabe
1df60186f0 fix!: authors instead of author 2022-05-31 23:05:13 +02:00
Moritz
b8e297c5ba Merge pull request #1043 from mandiant/mr-tz-patch-1
Update build.yml
2022-05-31 22:40:45 +02:00
Zander Work
486ffed4bd ignore .git/ when loading capa rules (#1038)
* ignore .git/ when loading capa rules

* moved check outside of for loop
2022-05-31 22:35:28 +02:00
Moritz
cb703aea18 Update build.yml 2022-05-31 22:33:53 +02:00
Willi Ballenthin
5084cb0887 Merge branch 'feature-981' of github.com:mandiant/capa into feature-981 2022-05-31 07:45:38 -06:00
Willi Ballenthin
5d6c12d900 sync rules 2022-05-31 07:45:13 -06:00
Capa Bot
2f47fddda9 Sync capa rules submodule 2022-05-31 13:42:34 +00:00
Willi Ballenthin
42e2c53e5e wip: pydantic result document 2022-05-31 07:40:50 -06:00
Willi Ballenthin
8080752815 freeze: pass descriptions around 2022-05-31 07:40:30 -06:00
Willi Ballenthin
2dec484676 typing fixes 2022-05-31 07:40:20 -06:00
Moritz
3d0a59cf74 Update pyinstaller.spec (#1042)
* Update pyinstaller.spec
2022-05-31 13:29:04 +02:00
Moritz
5169568c3b Merge pull request #1034 from mandiant/dependabot/pip/pyinstaller-5.1
build(deps-dev): bump pyinstaller from 5.0.1 to 5.1
2022-05-31 12:33:38 +02:00
dependabot[bot]
44a5dc0cd0 build(deps-dev): bump pyinstaller from 5.0.1 to 5.1
Bumps [pyinstaller](https://github.com/pyinstaller/pyinstaller) from 5.0.1 to 5.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.0.1...v5.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-31 09:18:38 +00:00
Moritz
1f38004114 Merge pull request #1040 from mandiant/dependabot/pip/mypy-0.960
build(deps-dev): bump mypy from 0.950 to 0.960
2022-05-31 11:18:04 +02:00
Moritz
8e7143556b Merge pull request #1041 from mandiant/dependabot/pip/types-requests-2.27.29
build(deps-dev): bump types-requests from 2.27.27 to 2.27.29
2022-05-31 11:17:52 +02:00
dependabot[bot]
2f519cba30 build(deps-dev): bump types-requests from 2.27.27 to 2.27.29
Bumps [types-requests](https://github.com/python/typeshed) from 2.27.27 to 2.27.29.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-30 14:14:17 +00:00
dependabot[bot]
02444d801e build(deps-dev): bump mypy from 0.950 to 0.960
Bumps [mypy](https://github.com/python/mypy) from 0.950 to 0.960.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v0.950...v0.960)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-30 14:14:14 +00:00
dependabot[bot]
85d4991cb3 build(deps): bump pefile from 2021.9.3 to 2022.5.30
Bumps [pefile](https://github.com/erocarrera/pefile) from 2021.9.3 to 2022.5.30.
- [Release notes](https://github.com/erocarrera/pefile/releases)
- [Commits](https://github.com/erocarrera/pefile/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-30 14:14:11 +00:00
Willi Ballenthin
4ae4bab254 lint: use meta.authors 2022-05-26 12:02:47 -06:00
Mike Hunhoff
3514d5c05c dotnet: support file/function scope class and namespace features (#1030) 2022-05-26 11:19:31 -06:00
Willi Ballenthin
9236a36ef4 rule: factor out is subscope check 2022-05-26 10:24:31 -06:00
Willi Ballenthin
b2318ce957 features: remove freeze_(de)serialize with preference to freeze module 2022-05-25 17:19:56 -06:00
Willi Ballenthin
3879e33cce freeze: model each features separately 2022-05-25 17:12:02 -06:00
Willi Ballenthin
eb6de90059 changelog 2022-05-25 15:30:46 -06:00
Willi Ballenthin
6b633efdba freeze: fix schema to support overlapping functions 2022-05-25 15:28:02 -06:00
Willi Ballenthin
02cef8297c pep8 2022-05-25 15:27:56 -06:00
Willi Ballenthin
adb425aeb3 freeze: use pydantic for (de)serialization
also, move null extractor to its own namespace
2022-05-25 15:09:31 -06:00
Willi Ballenthin
b1fa5be7b1 show-features: render features in a tree to better group scopes 2022-05-25 15:08:44 -06:00
Willi Ballenthin
d7cfa4ee96 features: make features implement __lt__ 2022-05-25 15:08:26 -06:00
Willi Ballenthin
46a79f43bb Merge pull request #1037 from mandiant/gh-codespaces
add Github Codespaces config
2022-05-25 11:38:15 -06:00
Willi Ballenthin
5a71caf09c add Github Codespaces config 2022-05-25 14:44:10 +00:00
Willi Ballenthin
a4003d7d91 tests: fix scripts using json document 2022-05-24 14:50:17 -06:00
Willi Ballenthin
b35fe6cdb2 json, render: work with and serialize addresses 2022-05-24 13:52:56 -06:00
Willi Ballenthin
d728869690 freeze: mypy and pep8 2022-05-24 13:52:40 -06:00
Willi Ballenthin
6b6dd70110 freeze: use address abstraction 2022-05-24 12:30:06 -06:00
Willi Ballenthin
fc9681f6d5 helpers: fix import loop 2022-05-24 12:29:56 -06:00
Willi Ballenthin
e4caa1d729 base extractor: use handles 2022-05-24 12:29:38 -06:00
Willi Ballenthin
4a577fabfc Merge pull request #1031 from mandiant/fix/ida-plugin
fix: rule generator handles
2022-05-23 11:29:48 -06:00
Willi Ballenthin
314ad4ea4d Merge pull request #1028 from mandiant/fix-988
elf: better detect Linux OS
2022-05-23 11:29:13 -06:00
Willi Ballenthin
2b446c75dd Merge pull request #1032 from mandiant/dependabot/pip/types-requests-2.27.27
build(deps-dev): bump types-requests from 2.27.25 to 2.27.27
2022-05-23 11:28:51 -06:00
Willi Ballenthin
ecf22c2c50 Merge pull request #1033 from mandiant/dependabot/pip/psutil-5.9.1
build(deps-dev): bump psutil from 5.9.0 to 5.9.1
2022-05-23 11:28:43 -06:00
dependabot[bot]
6f234b57fc build(deps-dev): bump psutil from 5.9.0 to 5.9.1
Bumps [psutil](https://github.com/giampaolo/psutil) from 5.9.0 to 5.9.1.
- [Release notes](https://github.com/giampaolo/psutil/releases)
- [Changelog](https://github.com/giampaolo/psutil/blob/master/HISTORY.rst)
- [Commits](https://github.com/giampaolo/psutil/compare/release-5.9.0...release-5.9.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-23 14:10:27 +00:00
dependabot[bot]
ddb6c810eb build(deps-dev): bump types-requests from 2.27.25 to 2.27.27
Bumps [types-requests](https://github.com/python/typeshed) from 2.27.25 to 2.27.27.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-23 14:10:21 +00:00
Capa Bot
8f2c9cbd11 Sync capa rules submodule 2022-05-20 14:36:37 +00:00
Moritz Raabe
a4f0c1c04c fix: rule generator handles 2022-05-19 20:43:02 +02:00
Willi Ballenthin
7642db332a Merge pull request #1029 from mandiant/feat/981-add-ida-handles
feat: add IDA handles and type annotations
2022-05-17 09:33:46 -06:00
Moritz Raabe
8e1f710312 fix: add __str__ 2022-05-12 18:49:53 +02:00
Moritz Raabe
83cae29dbe ci: temporarily test on PR 2022-05-12 18:40:45 +02:00
Moritz Raabe
b2853cc56b feat: update dnfile tests and extractor 2022-05-12 18:37:02 +02:00
Moritz Raabe
d8c9941f6b fix: filter address 2022-05-12 16:39:36 +02:00
Moritz Raabe
716a73dfb4 feat: add handles and type annotations 2022-05-12 15:42:25 +02:00
Capa Bot
cded1d3125 Sync capa-testfiles submodule 2022-05-12 06:35:04 +00:00
Willi Ballenthin
7b05fc4180 pep8 + mypy 2022-05-11 13:25:25 -06:00
Willi Ballenthin
78e9280a93 Merge branch 'master' into feature-981 2022-05-11 13:20:48 -06:00
Willi Ballenthin
ca2adb85b0 Merge pull request #1026 from mandiant/dependabot/pip/types-colorama-0.4.14
build(deps-dev): bump types-colorama from 0.4.13 to 0.4.14
2022-05-11 13:12:39 -06:00
Willi Ballenthin
fca612e873 Merge pull request #1025 from mandiant/dependabot/pip/types-tabulate-0.8.9
build(deps-dev): bump types-tabulate from 0.8.8 to 0.8.9
2022-05-11 13:12:30 -06:00
Willi Ballenthin
07e35780d3 Merge branch 'master' into fix-988 2022-05-11 13:10:45 -06:00
Willi Ballenthin
521cbf9104 pep8 2022-05-11 13:10:08 -06:00
Willi Ballenthin
a6427364e0 tests: add test demonstrating elf OS detection 2022-05-11 13:09:12 -06:00
Willi Ballenthin
c30ce6e73a changelog 2022-05-11 12:54:30 -06:00
Willi Ballenthin
e4abe46d16 elf: better detect Linux OS
closes #988
2022-05-11 12:53:17 -06:00
Willi Ballenthin
71cf19b850 render: handle dn tokens 2022-05-11 12:51:42 -06:00
Willi Ballenthin
a734a045ae dnfile: address: use rva 2022-05-11 12:51:29 -06:00
Capa Bot
141da27715 Sync capa rules submodule 2022-05-11 16:34:46 +00:00
dependabot[bot]
7971b94001 build(deps-dev): bump types-colorama from 0.4.13 to 0.4.14
Bumps [types-colorama](https://github.com/python/typeshed) from 0.4.13 to 0.4.14.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-09 14:13:43 +00:00
dependabot[bot]
95b3c6a594 build(deps-dev): bump types-tabulate from 0.8.8 to 0.8.9
Bumps [types-tabulate](https://github.com/python/typeshed) from 0.8.8 to 0.8.9.
- [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>
2022-05-09 14:13:40 +00:00
Mike Hunhoff
0d849142ba dotnet: emit mixed mode characteristic (#1024) 2022-05-06 14:32:06 -06:00
Moritz
f96c7379e0 Merge pull request #1021 from mandiant/ci/fix-build-workflow
ci: build on PR
2022-05-06 22:07:53 +02:00
Mike Hunhoff
6fb9dd961a dotnet: emit unmanaged call characteristic (#1023) 2022-05-06 13:05:48 -06:00
Mike Hunhoff
a9c9b3cea8 dotnet: extract file function names (#1015) 2022-05-06 08:34:50 -06:00
Moritz
ff2810654e Merge pull request #1018 from mandiant/dependabot/pip/vivisect-1.0.8
build(deps): bump vivisect from 1.0.7 to 1.0.8
2022-05-06 16:30:38 +02:00
Moritz Raabe
80e4161b40 ci: build on PR 2022-05-06 16:29:54 +02:00
Moritz
0473ce3259 Merge pull request #1017 from mandiant/dependabot/pip/mypy-0.950
build(deps-dev): bump mypy from 0.942 to 0.950
2022-05-06 15:50:15 +02:00
Moritz
0a211c1461 Merge pull request #1019 from mandiant/dependabot/pip/types-termcolor-1.1.4
build(deps-dev): bump types-termcolor from 1.1.3 to 1.1.4
2022-05-06 15:50:00 +02:00
Moritz Raabe
5573794a1f dep: bump viv-utils 2022-05-06 15:49:04 +02:00
dependabot[bot]
d0a1313f33 build(deps-dev): bump types-termcolor from 1.1.3 to 1.1.4
Bumps [types-termcolor](https://github.com/python/typeshed) from 1.1.3 to 1.1.4.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-05 20:21:15 +00:00
dependabot[bot]
aca4f27ee8 build(deps-dev): bump mypy from 0.942 to 0.950
Bumps [mypy](https://github.com/python/mypy) from 0.942 to 0.950.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v0.942...v0.950)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-05 20:20:18 +00:00
dependabot[bot]
bcd00004b8 build(deps-dev): bump types-tabulate from 0.8.7 to 0.8.8 (#1009) 2022-05-05 14:20:16 -06:00
dependabot[bot]
eefc0a9632 build(deps-dev): bump pyinstaller from 5.0 to 5.0.1 (#1008) 2022-05-05 14:19:56 -06:00
dependabot[bot]
dcf43b6fee build(deps): bump vivisect from 1.0.7 to 1.0.8
Bumps [vivisect](https://github.com/vivisect/vivisect) from 1.0.7 to 1.0.8.
- [Release notes](https://github.com/vivisect/vivisect/releases)
- [Changelog](https://github.com/vivisect/vivisect/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/vivisect/vivisect/compare/v1.0.7...v1.0.8)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-05 20:19:53 +00:00
dependabot[bot]
6d218aaf0d build(deps-dev): bump types-requests from 2.27.20 to 2.27.25 (#1007) 2022-05-05 14:19:35 -06:00
dependabot[bot]
20d80c1a2e build(deps-dev): bump types-colorama from 0.4.12 to 0.4.13 (#1010) 2022-05-05 14:19:21 -06:00
Mike Hunhoff
24c4215820 dotnet: add file string parsing (#1012) 2022-05-05 13:39:29 -06:00
dependabot[bot]
0066b3f33a build(deps): bump dnfile from 0.10.0 to 0.11.0 (#1004) 2022-05-05 13:28:48 -06:00
Moritz
daf483309e fix: temporarily accept x32/x64 flavors but ignore (#1014) 2022-05-05 20:19:35 +02:00
Capa Bot
49b1296d6e Sync capa rules submodule 2022-04-27 17:18:08 +00:00
Moritz
9f12f069ee ci: fix build (#980)
* ci: fix build

* fix: newest PyInstaller version

* fix: logo path

* fix: logo path 2

* fix: logo path 3

* fix: icon another way

* fix: remove icon for now

* ci: only build after tests succeed

* ci: add workflow_run check
2022-04-27 15:09:58 +02:00
Capa Bot
10852a5d96 Sync capa rules submodule 2022-04-27 11:36:08 +00:00
Moritz
3347245c2e Merge pull request #1003 from mandiant/dependabot/pip/viv-utils-flirt--0.7.1
build(deps): bump viv-utils[flirt] from 0.6.11 to 0.7.1
2022-04-26 21:09:13 +02:00
Moritz
3e8e88c363 Merge pull request #1002 from mandiant/dependabot/pip/types-requests-2.27.20
build(deps-dev): bump types-requests from 2.27.19 to 2.27.20
2022-04-26 21:08:54 +02:00
Moritz
e4dfa45057 Merge pull request #1001 from mandiant/dependabot/pip/types-pyyaml-6.0.7
build(deps-dev): bump types-pyyaml from 6.0.6 to 6.0.7
2022-04-26 21:08:44 +02:00
Moritz
b58e90e8dd Merge pull request #1000 from mandiant/dependabot/pip/pytest-7.1.2
build(deps-dev): bump pytest from 7.1.1 to 7.1.2
2022-04-26 21:08:31 +02:00
dependabot[bot]
0e18cea11a build(deps): bump viv-utils[flirt] from 0.6.11 to 0.7.1
Bumps [viv-utils[flirt]](https://github.com/williballenthin/viv-utils) from 0.6.11 to 0.7.1.
- [Release notes](https://github.com/williballenthin/viv-utils/releases)
- [Commits](https://github.com/williballenthin/viv-utils/compare/v0.6.11...v0.7.1)

---
updated-dependencies:
- dependency-name: viv-utils[flirt]
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-25 14:13:34 +00:00
dependabot[bot]
e950932e43 build(deps-dev): bump types-requests from 2.27.19 to 2.27.20
Bumps [types-requests](https://github.com/python/typeshed) from 2.27.19 to 2.27.20.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-25 14:13:31 +00:00
dependabot[bot]
45738773ca build(deps-dev): bump types-pyyaml from 6.0.6 to 6.0.7
Bumps [types-pyyaml](https://github.com/python/typeshed) from 6.0.6 to 6.0.7.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-25 14:13:28 +00:00
dependabot[bot]
054bcc9cb8 build(deps-dev): bump pytest from 7.1.1 to 7.1.2
Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.1.1 to 7.1.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.1.1...7.1.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-25 14:13:26 +00:00
Moritz
4d49b749c5 Merge pull request #998 from mandiant/dependabot/pip/types-tabulate-0.8.7
build(deps-dev): bump types-tabulate from 0.8.6 to 0.8.7
2022-04-20 17:53:08 +02:00
Moritz
4d86774266 Merge pull request #997 from mandiant/dependabot/pip/types-colorama-0.4.12
build(deps-dev): bump types-colorama from 0.4.10 to 0.4.12
2022-04-20 17:52:57 +02:00
Moritz
20171fe4f2 Merge pull request #995 from mandiant/dependabot/pip/types-psutil-5.8.22
build(deps-dev): bump types-psutil from 5.8.20 to 5.8.22
2022-04-20 17:52:40 +02:00
dependabot[bot]
308a47a784 build(deps-dev): bump types-tabulate from 0.8.6 to 0.8.7
Bumps [types-tabulate](https://github.com/python/typeshed) from 0.8.6 to 0.8.7.
- [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>
2022-04-20 15:48:18 +00:00
dependabot[bot]
2226bf0faa build(deps-dev): bump types-psutil from 5.8.20 to 5.8.22
Bumps [types-psutil](https://github.com/python/typeshed) from 5.8.20 to 5.8.22.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-20 15:48:17 +00:00
dependabot[bot]
65cf8509f9 build(deps-dev): bump types-colorama from 0.4.10 to 0.4.12
Bumps [types-colorama](https://github.com/python/typeshed) from 0.4.10 to 0.4.12.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-20 15:48:15 +00:00
Moritz
523ec7f453 Merge pull request #996 from mandiant/dependabot/pip/types-pyyaml-6.0.6
build(deps-dev): bump types-pyyaml from 6.0.5 to 6.0.6
2022-04-20 17:47:32 +02:00
Moritz
8a1bc39eb2 Merge pull request #994 from mandiant/dependabot/pip/types-requests-2.27.19
build(deps-dev): bump types-requests from 2.27.16 to 2.27.19
2022-04-20 17:45:36 +02:00
dependabot[bot]
fd1785fe65 build(deps-dev): bump types-pyyaml from 6.0.5 to 6.0.6
Bumps [types-pyyaml](https://github.com/python/typeshed) from 6.0.5 to 6.0.6.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-18 14:12:43 +00:00
dependabot[bot]
45c22a24a6 build(deps-dev): bump types-requests from 2.27.16 to 2.27.19
Bumps [types-requests](https://github.com/python/typeshed) from 2.27.16 to 2.27.19.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-18 14:12:38 +00:00
Willi Ballenthin
c236293185 features: insn: number: allow floats, too 2022-04-08 18:41:19 -06:00
Willi Ballenthin
bfb6d4d142 dn: fix access to ctx 2022-04-08 18:41:09 -06:00
Willi Ballenthin
723efe1755 address: better implement .NET token 2022-04-08 18:40:58 -06:00
Willi Ballenthin
e029547035 show-features: learn to use Addresses 2022-04-08 18:33:49 -06:00
Willi Ballenthin
d9ede95cf7 dnfile: use Address 2022-04-08 18:33:39 -06:00
Willi Ballenthin
70c3487bc7 address: better implement .NET token 2022-04-08 18:33:23 -06:00
Willi Ballenthin
808b7fb4dc dnfile: fix types 2022-04-08 18:33:12 -06:00
Willi Ballenthin
ed1009096d Merge branch 'master' of github.com:mandiant/capa into feature-981 2022-04-08 16:01:59 -06:00
Mike Hunhoff
580a2d7e45 dotnet: basic detection and feature extraction (#987) 2022-04-08 14:55:00 -06:00
Willi Ballenthin
87d3d6c577 smda: use Addresses 2022-04-08 13:15:04 -06:00
Willi Ballenthin
ae87fa1785 elf: use addresses 2022-04-08 12:49:48 -06:00
Willi Ballenthin
2b00bc0fdb pep8 2022-04-08 12:46:43 -06:00
Willi Ballenthin
43b8ad80c7 pefile: extract Addresses 2022-04-08 12:45:46 -06:00
Willi Ballenthin
65b462f62c render: format various address types differently 2022-04-08 12:39:10 -06:00
Willi Ballenthin
7e7740cf77 viv: insn: use handles for code merged from master 2022-04-08 12:38:46 -06:00
Willi Ballenthin
a3d1b1403c address: fix min value for unsigned addresses 2022-04-08 12:38:21 -06:00
Willi Ballenthin
31977e6523 changelog 2022-04-08 12:19:50 -06:00
Willi Ballenthin
9164713dd9 Merge branch 'dotnet-main' of github.com:mandiant/capa into feature-981 2022-04-08 12:17:16 -06:00
Willi Ballenthin
bfb01e3729 extractor: viv: use handles throughout 2022-04-08 11:54:27 -06:00
Willi Ballenthin
fc1709ba6c extractor: add types throughout 2022-04-08 11:53:42 -06:00
Willi Ballenthin
1b79aae836 extractor: introduce standardized handles for function, bb, insn 2022-04-08 11:46:07 -06:00
Willi Ballenthin
6355fb3f3e add Address abstraction to handle various ways of identifing things in files 2022-04-08 11:44:24 -06:00
Moritz
c8a772d19a test: update dotnet dirs and sync master (#984) 2022-04-08 09:34:22 -06:00
Capa Bot
5bc44aef0f Sync capa-testfiles submodule 2022-04-08 10:34:02 +00:00
Willi Ballenthin
b455b67da3 Merge pull request #977 from mandiant/feature-320
extract extra offset/number features
2022-04-07 14:20:10 -06:00
Willi Ballenthin
351d70aafe smda: implement additional offset and number features 2022-04-07 12:56:24 -06:00
Willi Ballenthin
8a2276f398 smda: implement operand number/offset features
cause its not too hard
2022-04-07 12:48:25 -06:00
Moritz
65552575f8 Update dotnet-main (#979)
* Sync capa rules submodule

* Sync capa-testfiles submodule

* Sync capa rules submodule

* changelog

* *: remove /x32 and /x64 flavors from number and offset features

* *: remove more references to /x32 and /x64

* linter: accept instruction scope

* rules: fix max operand index (4)

* API: better support A/W functions

* vverbose: show lib rule matches

* main: accept multiple paths to rules

* main: fix removal of default rules path

* lint: fix rules path

* changelog

* capa_as_library: fix rules path is list now

* main: better handle multiple rules paths

* main: bail if python 3.6 or below

closes #964

* ida: readme: remove python 3.6 support

* capa2yara: fix rules paths

* render: meta: display rule paths on separate lines

closes #971

* render: verbose: add doc

* verbose: make rule path multiline more concise

* vverbose: don't show examples in output

closes #970

* vverbose: render subscope name, like "basic block:"

closes #963

* build(deps-dev): bump pytest from 7.0.1 to 7.1.1

Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.0.1 to 7.1.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.0.1...7.1.1)

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

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

* ci: build: update pip and setuptools

* ci: build: bump pyinstall to v4.10

* Sync capa rules submodule

* Dotnet mixed mode detect (#969)

* feat: start dotnet detection (#955)

* feat: start dotnet detection

* Apply suggestions from code review

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

* refactor: dn instead of dotnet

* refactor: format branches, extractor reorg

* refactor: format selection and dotnet detect

* feat: get format, arch, os

* refactor: log errors and exceptions

* ci: also test and build for dotnet-main dev

* fix: import path

* fix: circular dep

* fix: remove buf argument
feat: get runtime meta data

* fix: log unsupported runtime error

* fix: type ignore

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

* fix: imports and add tests

* feat: detect mixed mode and tests

* feat: start dotnet detection (#955)

* feat: start dotnet detection

* Apply suggestions from code review

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

* refactor: dn instead of dotnet

* refactor: format branches, extractor reorg

* refactor: format selection and dotnet detect

* feat: get format, arch, os

* refactor: log errors and exceptions

* ci: also test and build for dotnet-main dev

* fix: import path

* fix: circular dep

* fix: remove buf argument
feat: get runtime meta data

* fix: log unsupported runtime error

* fix: type ignore

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

* fix: imports and add tests

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

* test: checkout submodules recursively

Co-authored-by: Capa Bot <capa-dev@mandiant.com>
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-07 17:45:29 +02:00
Capa Bot
4c84a77053 Sync capa rules submodule 2022-04-07 07:50:51 +00:00
Willi Ballenthin
6b810a1f72 ida: insn: look for numbers in displ, not phrase 2022-04-06 15:41:17 -06:00
Willi Ballenthin
c36bde0f2d ida: insn: ignore numbers when SIB present 2022-04-06 15:38:04 -06:00
Willi Ballenthin
1a44dd8a2b insn: better detect offset/numbers 2022-04-06 15:12:59 -06:00
Willi Ballenthin
1c7b6bcf7d fixtures: use function that IDA doesn't recognize as lib func 2022-04-06 15:07:35 -06:00
Willi Ballenthin
e2c6f5e393 ida: insn: use .ea not .va 2022-04-06 15:03:24 -06:00
Willi Ballenthin
85d5043992 changelog 2022-04-06 14:59:24 -06:00
Willi Ballenthin
47dfeafdc8 ida, viv: implement extra offset/number extraction 2022-04-06 14:57:51 -06:00
Willi Ballenthin
b843cef986 tests: add tests for #320 2022-04-06 14:38:56 -06:00
Willi Ballenthin
0e95691cde tests: fixtures: enable assertions against instruction scope 2022-04-06 14:38:33 -06:00
Willi Ballenthin
54aa14c4f5 Merge pull request #975 from mandiant/fix-936
ci: build: bump pyinstall to v4.10
2022-04-06 14:20:21 -06:00
Willi Ballenthin
dfcb3cc2ea ci: build: bump pyinstall to v4.10 2022-04-06 14:17:59 -06:00
Willi Ballenthin
587202ce43 ci: build: update pip and setuptools 2022-04-06 14:03:44 -06:00
Willi Ballenthin
6b2529bc80 Merge pull request #916 from mandiant/dependabot/pip/pytest-7.1.1
build(deps-dev): bump pytest from 7.0.1 to 7.1.1
2022-04-06 13:44:40 -06:00
Willi Ballenthin
52137f310a Merge pull request #974 from mandiant/feature-vverbose-subscope
in vverbose mode, show subscope name
2022-04-06 13:44:15 -06:00
Willi Ballenthin
ad90145aa7 Merge pull request #973 from mandiant/feature-remove-example-vverbose
vverbose: don't show examples in output
2022-04-06 13:42:12 -06:00
Willi Ballenthin
05f7ac0802 Merge pull request #972 from mandiant/feature-many-rule-paths-meta
render: meta: display rule paths on separate lines
2022-04-06 13:41:48 -06:00
Willi Ballenthin
fccca823c5 verbose: make rule path multiline more concise 2022-04-06 13:41:05 -06:00
Willi Ballenthin
441373ea13 vverbose: render subscope name, like "basic block:"
closes #963
2022-04-06 13:33:56 -06:00
Capa Bot
57d2df4922 Sync capa rules submodule 2022-04-06 19:28:26 +00:00
Willi Ballenthin
632e778376 vverbose: don't show examples in output
closes #970
2022-04-06 13:24:36 -06:00
Willi Ballenthin
d47b1503b2 render: verbose: add doc 2022-04-06 13:21:11 -06:00
Willi Ballenthin
938c75737b render: meta: display rule paths on separate lines
closes #971
2022-04-06 13:18:06 -06:00
Willi Ballenthin
55a5d10859 Merge pull request #961 from mandiant/feature-remove-flavors
remove /x32 and /x64 flavors of number and offset features
2022-04-06 12:57:18 -06:00
Willi Ballenthin
0c354cf268 capa2yara: fix rules paths 2022-04-06 12:36:28 -06:00
Willi Ballenthin
485600801c ida: readme: remove python 3.6 support 2022-04-06 12:16:06 -06:00
Willi Ballenthin
4916933139 main: bail if python 3.6 or below
closes #964
2022-04-06 12:14:53 -06:00
Capa Bot
73f1eb9c30 Sync capa rules submodule 2022-04-06 18:08:02 +00:00
Willi Ballenthin
e788384d42 main: better handle multiple rules paths 2022-04-06 12:05:01 -06:00
Capa Bot
633d8df1a4 Sync capa-testfiles submodule 2022-04-06 17:21:09 +00:00
Willi Ballenthin
aff72ad983 capa_as_library: fix rules path is list now 2022-04-06 11:07:34 -06:00
Willi Ballenthin
c9763c4d70 Merge branch 'master' into feature-remove-flavors 2022-04-06 11:05:05 -06:00
Capa Bot
931a13e505 Sync capa rules submodule 2022-04-06 17:04:16 +00:00
Moritz Raabe
97e76a88e3 fix: imports and add tests 2022-04-06 17:30:51 +02:00
Moritz
b5be876e61 feat: start dotnet detection (#955)
* feat: start dotnet detection

* Apply suggestions from code review

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

* refactor: dn instead of dotnet

* refactor: format branches, extractor reorg

* refactor: format selection and dotnet detect

* feat: get format, arch, os

* refactor: log errors and exceptions

* ci: also test and build for dotnet-main dev

* fix: import path

* fix: circular dep

* fix: remove buf argument
feat: get runtime meta data

* fix: log unsupported runtime error

* fix: type ignore

Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2022-04-06 11:33:14 +02:00
Willi Ballenthin
7370a8f296 changelog 2022-04-05 17:21:04 -06:00
Willi Ballenthin
11b773573e lint: fix rules path 2022-04-05 17:17:44 -06:00
Willi Ballenthin
67dc2cb0fa main: fix removal of default rules path 2022-04-05 17:17:35 -06:00
Willi Ballenthin
bad9ecf3b1 main: accept multiple paths to rules 2022-04-05 17:14:53 -06:00
Willi Ballenthin
ef835649fd vverbose: show lib rule matches 2022-04-05 16:57:36 -06:00
Willi Ballenthin
e9bb56f3cf API: better support A/W functions 2022-04-05 14:54:15 -06:00
Willi Ballenthin
58acc9c2b7 rules: fix max operand index (4) 2022-04-05 14:53:58 -06:00
Willi Ballenthin
f923a4ea9b linter: accept instruction scope 2022-04-05 12:24:41 -06:00
Willi Ballenthin
5957dfecf0 Merge branch 'feature-remove-flavors' of github.com:mandiant/capa into feature-remove-flavors 2022-04-05 10:41:41 -06:00
Willi Ballenthin
aee61b35e4 *: remove more references to /x32 and /x64 2022-04-05 10:41:03 -06:00
Willi Ballenthin
169d5ab826 Merge branch 'master' into feature-remove-flavors 2022-04-05 10:37:18 -06:00
Willi Ballenthin
de312d87dc Merge pull request #960 from mandiant/feature-py37
upgrade min python version to 3.7
2022-04-05 10:36:33 -06:00
Willi Ballenthin
ecabd557a7 *: remove /x32 and /x64 flavors from number and offset features 2022-04-05 10:35:41 -06:00
Willi Ballenthin
f246a01484 changelog 2022-04-05 10:24:55 -06:00
Willi Ballenthin
0617b87f36 ci: no longer test against py3.6 2022-04-05 10:19:09 -06:00
Willi Ballenthin
715ac64ae6 changelog 2022-04-05 10:19:04 -06:00
Willi Ballenthin
78c0afe006 setup: min python version is now 3.7 2022-04-05 10:18:55 -06:00
Willi Ballenthin
df03932f89 gitignore 2022-04-04 16:54:51 -06:00
dependabot[bot]
15196c847a build(deps-dev): bump pytest from 7.0.1 to 7.1.1
Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.0.1 to 7.1.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.0.1...7.1.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-04 22:43:41 +00:00
Willi Ballenthin
b2b4471851 Merge pull request #930 from mandiant/feature-insn-scope
feature: instruction scope
2022-04-04 16:42:56 -06:00
Willi Ballenthin
5ffb73c5f5 ida: insn: extract operand number and offset features 2022-04-04 15:13:43 -06:00
Willi Ballenthin
ef93fcc89e tests: smda: xfail operand number/offset features 2022-04-04 12:05:15 -06:00
Willi Ballenthin
0af60d9a7e freeze: fix mypy 2022-04-04 12:01:13 -06:00
Willi Ballenthin
750803c3cc freeze: register operand features 2022-04-04 11:57:02 -06:00
Willi Ballenthin
b318b0a288 freeze: fix freeze_deserialize for features with multiple args 2022-04-04 11:56:47 -06:00
Willi Ballenthin
2989af0a3f features: use ABC to denote abstract classes 2022-04-04 11:49:51 -06:00
Moritz
3f168772aa Merge pull request #934 from mandiant/dependabot/pip/types-colorama-0.4.10
build(deps-dev): bump types-colorama from 0.4.9 to 0.4.10
2022-04-04 17:42:16 +02:00
Moritz
2ba25f096d Merge pull request #935 from mandiant/dependabot/pip/types-requests-2.27.16
build(deps-dev): bump types-requests from 2.27.15 to 2.27.16
2022-04-04 17:42:02 +02:00
Moritz
6d35e19571 Merge pull request #933 from mandiant/dependabot/pip/tqdm-4.64.0
build(deps): bump tqdm from 4.63.1 to 4.64.0
2022-04-04 17:41:56 +02:00
dependabot[bot]
0d9583f7e7 build(deps-dev): bump types-requests from 2.27.15 to 2.27.16
Bumps [types-requests](https://github.com/python/typeshed) from 2.27.15 to 2.27.16.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-04 14:12:27 +00:00
dependabot[bot]
fe6b18135c build(deps-dev): bump types-colorama from 0.4.9 to 0.4.10
Bumps [types-colorama](https://github.com/python/typeshed) from 0.4.9 to 0.4.10.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-04 14:12:24 +00:00
dependabot[bot]
e89fe57def build(deps): bump tqdm from 4.63.1 to 4.64.0
Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.63.1 to 4.64.0.
- [Release notes](https://github.com/tqdm/tqdm/releases)
- [Commits](https://github.com/tqdm/tqdm/compare/v4.63.1...v4.64.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-04 14:12:21 +00:00
Willi Ballenthin
85b1d50945 isort 2022-03-31 10:40:48 -06:00
Willi Ballenthin
856443319c viv: insn: fix OperandNumber reference 2022-03-31 10:39:18 -06:00
Willi Ballenthin
9da4ff10da *: rename OperandImmediate to OperandNumber 2022-03-31 10:37:06 -06:00
Willi Ballenthin
76831e9b9d changelog 2022-03-30 13:20:51 -06:00
Willi Ballenthin
997daf537e viv: insn: extract OperandOffset and OperandImmediate 2022-03-30 13:14:08 -06:00
Willi Ballenthin
c7aadca25c tests: demonstrate OperandOffset and OperandImmediate 2022-03-30 13:13:50 -06:00
Willi Ballenthin
6cbbd4d97f rules: parse OperandOffset and OperandImmediate features 2022-03-30 13:13:30 -06:00
Willi Ballenthin
e4c5ec278d features: insn: define OperandImmediate and OperandOffset 2022-03-30 13:13:07 -06:00
Willi Ballenthin
cce1e41519 formatting 2022-03-30 13:12:49 -06:00
Willi Ballenthin
b942050c4e features: viv: factor out operand feature extraction 2022-03-30 09:58:08 -06:00
Willi Ballenthin
d8d671e36f rules: add global scope features to file scope 2022-03-30 09:40:43 -06:00
Willi Ballenthin
49adb8de0c pep8 2022-03-29 13:00:28 -06:00
Willi Ballenthin
fb6b60bee3 tests: add tests demonstrating instruction (sub)scope matching 2022-03-29 12:58:38 -06:00
Willi Ballenthin
e0fca277f2 rules: update valid features per scope 2022-03-29 12:58:27 -06:00
Willi Ballenthin
0effb5f8b0 changelog 2022-03-29 12:33:55 -06:00
Willi Ballenthin
1839746bf8 main: factor out matching at instruction scope 2022-03-29 12:29:54 -06:00
Willi Ballenthin
1a28c324f1 rules: doc 2022-03-29 12:26:39 -06:00
Willi Ballenthin
c1b28f58d0 rules: don't use global features to downselect rules
closes #931
2022-03-29 12:25:27 -06:00
Willi Ballenthin
565e4e0a2f Merge branch 'feature-insn-scope' of github.com:mandiant/capa into feature-insn-scope 2022-03-29 11:52:45 -06:00
Willi Ballenthin
7487da89a1 Merge branch 'master' into feature-insn-scope 2022-03-29 11:51:14 -06:00
Willi Ballenthin
fe5d88585c setup: bump black to 22.3.0 to fix CI 2022-03-29 11:40:34 -06:00
Willi Ballenthin
bd6e62e9bf Update scripts/lint.py
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2022-03-29 11:26:21 -06:00
Willi Ballenthin
b76930d2a3 main: split out basic block feature, match extraction 2022-03-28 13:47:53 -06:00
Willi Ballenthin
00d439f681 main: rename find_code_capabilities 2022-03-28 13:29:06 -06:00
Willi Ballenthin
963cfbf380 pep8 2022-03-28 13:17:35 -06:00
Willi Ballenthin
031ea167e8 add pycodestyle config 2022-03-28 13:17:18 -06:00
Willi Ballenthin
dde52f2bc8 pep8 2022-03-28 13:04:44 -06:00
Willi Ballenthin
46cc681eba tests: demonstrate instruct subscope rule extraction 2022-03-28 13:04:13 -06:00
Willi Ballenthin
b0619f4f01 rules: index instruction rules in ruleset 2022-03-28 13:03:58 -06:00
Willi Ballenthin
2baf05acdb rules: parse instruction subscope with implied AND 2022-03-28 12:55:09 -06:00
Willi Ballenthin
890870bf45 rules: let subscope blocks have descriptions 2022-03-28 12:54:54 -06:00
Willi Ballenthin
9da9c3aceb rules: add valid features for insn scope 2022-03-28 12:40:10 -06:00
Willi Ballenthin
c8fedb0f70 gitignore 2022-03-28 12:39:58 -06:00
Willi Ballenthin
a203f56bdb rules: add new scope "instruction" 2022-03-28 12:14:07 -06:00
Willi Ballenthin
18880c40d5 Merge pull request #927 from mandiant/dependabot/pip/mypy-0.942
build(deps-dev): bump mypy from 0.941 to 0.942
2022-03-28 11:26:06 -06:00
Willi Ballenthin
bd62661ef3 Merge pull request #928 from mandiant/dependabot/pip/types-requests-2.27.15
build(deps-dev): bump types-requests from 2.27.12 to 2.27.15
2022-03-28 11:25:57 -06:00
Willi Ballenthin
8d285c03ad Merge pull request #929 from mandiant/dependabot/pip/tqdm-4.63.1
build(deps): bump tqdm from 4.63.0 to 4.63.1
2022-03-28 11:25:25 -06:00
dependabot[bot]
7a4ee78805 build(deps): bump tqdm from 4.63.0 to 4.63.1
Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.63.0 to 4.63.1.
- [Release notes](https://github.com/tqdm/tqdm/releases)
- [Commits](https://github.com/tqdm/tqdm/compare/v4.63.0...v4.63.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-28 14:13:45 +00:00
dependabot[bot]
6105d2a36c build(deps-dev): bump types-requests from 2.27.12 to 2.27.15
Bumps [types-requests](https://github.com/python/typeshed) from 2.27.12 to 2.27.15.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-28 14:13:39 +00:00
dependabot[bot]
7db90ba35e build(deps-dev): bump mypy from 0.941 to 0.942
Bumps [mypy](https://github.com/python/mypy) from 0.941 to 0.942.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v0.941...v0.942)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-28 14:13:37 +00:00
Mike Hunhoff
fb34b1674b improve handling _ prefix added to library functions as compile/link artifact (#924) 2022-03-25 13:34:39 -06:00
Capa Bot
eaf978da0a Sync capa rules submodule 2022-03-24 09:43:38 +00:00
Capa Bot
ecea572192 Sync capa-testfiles submodule 2022-03-24 09:30:26 +00:00
Capa Bot
5552baa5e2 Sync capa rules submodule 2022-03-24 08:16:48 +00:00
Capa Bot
3b86ccc1a4 Sync capa rules submodule 2022-03-23 17:57:09 +00:00
Mike Hunhoff
8fd81d1098 Merge pull request #922 from mandiant/pin-smda-version-171
update pinned smda version
2022-03-22 12:46:35 -06:00
Mike Hunhoff
b7badede86 update pinned smda version 2022-03-22 12:25:41 -06:00
Mike Hunhoff
4c4e633395 Merge pull request #919 from mandiant/fix/917
fixes #917
2022-03-22 07:15:40 -06:00
Capa Bot
1cd5e89f85 Sync capa-testfiles submodule 2022-03-22 07:22:11 +00:00
Michael Hunhoff
768050f36c update CHANGELOG 2022-03-21 15:37:21 -06:00
Michael Hunhoff
f7f286db6c merge upstream 2022-03-21 15:35:49 -06:00
Mike Hunhoff
6d2ec59653 Merge pull request #918 from mandiant/fix/911
fixes #911
2022-03-21 15:33:45 -06:00
Michael Hunhoff
924d0111fd fixes #917 2022-03-21 15:28:39 -06:00
Michael Hunhoff
fe87838dbe fixes #911 2022-03-21 13:48:41 -06:00
Willi Ballenthin
1b2f0fc85d Merge pull request #913 from mandiant/dependabot/pip/types-tabulate-0.8.6
build(deps-dev): bump types-tabulate from 0.8.5 to 0.8.6
2022-03-21 10:36:55 -06:00
dependabot[bot]
e3bec5f186 build(deps-dev): bump types-tabulate from 0.8.5 to 0.8.6
Bumps [types-tabulate](https://github.com/python/typeshed) from 0.8.5 to 0.8.6.
- [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>
2022-03-21 16:36:24 +00:00
Willi Ballenthin
729b459701 Merge pull request #914 from mandiant/dependabot/pip/types-pyyaml-6.0.5
build(deps-dev): bump types-pyyaml from 6.0.4 to 6.0.5
2022-03-21 10:35:53 -06:00
Willi Ballenthin
1609bd5d07 Merge pull request #915 from mandiant/dependabot/pip/mypy-0.941
build(deps-dev): bump mypy from 0.940 to 0.941
2022-03-21 10:35:07 -06:00
dependabot[bot]
78222a530c build(deps-dev): bump types-pyyaml from 6.0.4 to 6.0.5
Bumps [types-pyyaml](https://github.com/python/typeshed) from 6.0.4 to 6.0.5.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-21 16:34:23 +00:00
dependabot[bot]
6613ee3c87 build(deps-dev): bump mypy from 0.940 to 0.941
Bumps [mypy](https://github.com/python/mypy) from 0.940 to 0.941.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v0.940...v0.941)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-21 16:34:23 +00:00
Willi Ballenthin
356b2f5ffb Merge pull request #912 from mandiant/dependabot/pip/types-colorama-0.4.9
build(deps-dev): bump types-colorama from 0.4.8 to 0.4.9
2022-03-21 10:33:37 -06:00
dependabot[bot]
a52cc7280f build(deps-dev): bump types-colorama from 0.4.8 to 0.4.9
Bumps [types-colorama](https://github.com/python/typeshed) from 0.4.8 to 0.4.9.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-21 14:13:40 +00:00
Capa Bot
0d38e3065c Sync capa rules submodule 2022-03-16 09:43:58 +00:00
Willi Ballenthin
3d13d501e7 Merge pull request #907 from mandiant/dependabot/pip/mypy-0.940
build(deps-dev): bump mypy from 0.931 to 0.940
2022-03-14 10:49:05 -06:00
Willi Ballenthin
ccf1f6205c Merge pull request #908 from mandiant/dependabot/pip/types-requests-2.27.12
build(deps-dev): bump types-requests from 2.27.11 to 2.27.12
2022-03-14 10:48:40 -06:00
dependabot[bot]
8d2b6df385 build(deps-dev): bump types-requests from 2.27.11 to 2.27.12
Bumps [types-requests](https://github.com/python/typeshed) from 2.27.11 to 2.27.12.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-14 14:14:24 +00:00
dependabot[bot]
62fd13c892 build(deps-dev): bump mypy from 0.931 to 0.940
Bumps [mypy](https://github.com/python/mypy) from 0.931 to 0.940.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v0.931...v0.940)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-14 14:14:21 +00:00
Capa Bot
cbf9f321c6 Sync capa-testfiles submodule 2022-03-14 10:18:05 +00:00
Moritz
c975305e95 Merge pull request #906 from mandiant/ci/remove-windows-2016
remove windows-2016
2022-03-14 10:18:11 +01:00
Willi Ballenthin
8afd12103d ci: build: try to use windows-2022 2022-03-11 17:40:35 -07:00
Moritz
5d106afca6 remove windows-2016 2022-03-11 08:38:09 +01:00
Capa Bot
8e43a23766 Sync capa rules submodule 2022-03-10 18:21:36 +00:00
Moritz
d9d72ad8df Merge pull request #905 from mandiant/v320
release v3.2.0
2022-03-03 19:59:17 +01:00
Moritz Raabe
1c5af81a4e release v3.2.0 2022-03-03 10:45:43 +01:00
Capa Bot
014fc4cda9 Sync capa rules submodule 2022-03-03 09:26:55 +00:00
Moritz
f29992741d Merge pull request #904 from mandiant/bump-viv-utils-vivisect
bump vivisect 1.0.7 and viv-utils 0.6.11
2022-03-02 08:59:21 +01:00
Moritz Raabe
5fa5f08607 bump vivisect 1.0.7 and viv-utils 0.6.11 2022-03-02 07:51:29 +01:00
Moritz
d4921c4a2f Merge pull request #902 from mandiant/feature/call5-ida
Feature/call5 ida
2022-03-01 09:05:33 +01:00
Moritz
64238062ca Merge pull request #901 from uckelman-sf/use_stdlib_typing
Don't require typing package; it's in the stdlib now
2022-03-01 09:00:25 +01:00
Moritz Raabe
00f977fff9 add call $+5 characteristic for IDA extractor 2022-03-01 08:50:06 +01:00
Moritz
c7ae2cd540 Merge pull request #899 from kn0wl3dge/feature/366-shellcode_obfs_call
Add characteristic "call $+5" feature with support for vivisect and smda
2022-03-01 08:48:50 +01:00
Moritz
293d88b1b9 Merge pull request #900 from mandiant/dependabot/pip/tqdm-4.63.0
build(deps): bump tqdm from 4.62.3 to 4.63.0
2022-02-28 22:28:52 +01:00
Joel Uckelman
fa2d19a5ca Update change log. 2022-02-28 16:43:18 +00:00
Joel Uckelman
f0f22041ca Remove requirement for separate typing package; typing is in the Python
stdlib from 3.5, and we require >= 3.6. From 3.7, installing the typing
package causes import failures.
2022-02-28 14:55:18 +00:00
dependabot[bot]
321316f99f build(deps): bump tqdm from 4.62.3 to 4.63.0
Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.62.3 to 4.63.0.
- [Release notes](https://github.com/tqdm/tqdm/releases)
- [Commits](https://github.com/tqdm/tqdm/compare/v4.62.3...v4.63.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-28 14:09:59 +00:00
Baptistin Boilot
4d915020a8 extractor: add characteristic(call $+5) feature extraction for vivisect and smda 2022-02-27 18:15:25 +01:00
Moritz
350eff27b7 Merge pull request #898 from mandiant/dependabot/pip/types-requests-2.27.11
build(deps-dev): bump types-requests from 2.27.10 to 2.27.11
2022-02-23 08:54:56 +01:00
dependabot[bot]
f9732db799 build(deps-dev): bump types-requests from 2.27.10 to 2.27.11
Bumps [types-requests](https://github.com/python/typeshed) from 2.27.10 to 2.27.11.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-22 18:29:53 +00:00
Moritz
73a7842a85 Merge pull request #897 from mandiant/dependabot/pip/types-requests-2.27.10
build(deps-dev): bump types-requests from 2.27.9 to 2.27.10
2022-02-22 19:26:01 +01:00
dependabot[bot]
b13a402675 build(deps-dev): bump types-requests from 2.27.9 to 2.27.10
Bumps [types-requests](https://github.com/python/typeshed) from 2.27.9 to 2.27.10.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-21 14:11:58 +00:00
Moritz
915cd5e4bc Merge pull request #896 from mandiant/dependabot/pip/pytest-7.0.1
build(deps-dev): bump pytest from 7.0.0 to 7.0.1
2022-02-15 10:23:25 +01:00
Moritz
151adfd5ed Merge pull request #894 from mandiant/dependabot/pip/ruamel-yaml-0.17.21
build(deps): bump ruamel-yaml from 0.17.20 to 0.17.21
2022-02-15 10:23:14 +01:00
Moritz
37519a038b Merge pull request #895 from mandiant/dependabot/pip/types-requests-2.27.9
build(deps-dev): bump types-requests from 2.27.8 to 2.27.9
2022-02-15 10:22:57 +01:00
dependabot[bot]
d0cc1b0b1d build(deps-dev): bump pytest from 7.0.0 to 7.0.1
Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.0.0 to 7.0.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.0.0...7.0.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-14 14:14:42 +00:00
dependabot[bot]
869ad9d561 build(deps-dev): bump types-requests from 2.27.8 to 2.27.9
Bumps [types-requests](https://github.com/python/typeshed) from 2.27.8 to 2.27.9.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-14 14:14:38 +00:00
dependabot[bot]
b31a4d6242 build(deps): bump ruamel-yaml from 0.17.20 to 0.17.21
Bumps [ruamel-yaml](https://sourceforge.net/p/ruamel-yaml/code/ci/default/tree) from 0.17.20 to 0.17.21.

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-14 14:14:34 +00:00
Moritz
439a855383 Merge pull request #893 from re-fox/master
Update capa_as_library.py
2022-02-14 10:36:42 +01:00
re-fox
37f51690d0 Update capa_as_library.py 2022-02-13 13:09:58 -05:00
Moritz
1bd807a1a0 Merge pull request #890 from mandiant/dependabot/pip/pyelftools-0.28
build(deps): bump pyelftools from 0.27 to 0.28
2022-02-07 21:25:23 +01:00
Moritz
ac6fef2e29 Merge pull request #889 from mandiant/dependabot/pip/pytest-7.0.0
build(deps-dev): bump pytest from 6.2.5 to 7.0.0
2022-02-07 21:24:52 +01:00
dependabot[bot]
e873086ddf build(deps): bump pyelftools from 0.27 to 0.28
Bumps [pyelftools](https://github.com/eliben/pyelftools) from 0.27 to 0.28.
- [Release notes](https://github.com/eliben/pyelftools/releases)
- [Changelog](https://github.com/eliben/pyelftools/blob/master/CHANGES)
- [Commits](https://github.com/eliben/pyelftools/compare/v0.27...v0.28)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-07 14:11:51 +00:00
dependabot[bot]
dd6159b062 build(deps-dev): bump pytest from 6.2.5 to 7.0.0
Bumps [pytest](https://github.com/pytest-dev/pytest) from 6.2.5 to 7.0.0.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/6.2.5...7.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-07 14:11:46 +00:00
Willi Ballenthin
7511563865 Merge pull request #888 from kn0wl3dge/fix/879_mbc_names
Add MBC names and IDs to the linting script
2022-02-06 11:49:58 -07:00
Capa Bot
9923216558 Sync capa rules submodule 2022-02-06 18:49:26 +00:00
Baptistin Boilot
d026d21073 linter: add MBC names and IDs to the linting script 2022-02-06 11:47:49 +01:00
Capa Bot
5bfe706b56 Sync capa rules submodule 2022-02-04 19:27:03 +00:00
Willi Ballenthin
2407015620 Merge pull request #887 from mandiant/dependabot/pip/types-colorama-0.4.8
build(deps-dev): bump types-colorama from 0.4.7 to 0.4.8
2022-01-31 12:55:33 -07:00
Willi Ballenthin
a8dd9d4bfd Merge branch 'master' into dependabot/pip/types-colorama-0.4.8 2022-01-31 12:55:27 -07:00
Willi Ballenthin
8d247bd1b6 Merge pull request #886 from mandiant/dependabot/pip/types-psutil-5.8.20
build(deps-dev): bump types-psutil from 5.8.19 to 5.8.20
2022-01-31 11:56:56 -07:00
Willi Ballenthin
533666d40c Merge branch 'master' into dependabot/pip/types-psutil-5.8.20 2022-01-31 11:56:50 -07:00
Willi Ballenthin
b85ee0b7a0 Merge pull request #885 from mandiant/dependabot/pip/black-22.1.0
build(deps-dev): bump black from 21.12b0 to 22.1.0
2022-01-31 11:56:25 -07:00
dependabot[bot]
9466038e62 build(deps-dev): bump types-colorama from 0.4.7 to 0.4.8
Bumps [types-colorama](https://github.com/python/typeshed) from 0.4.7 to 0.4.8.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-31 18:53:47 +00:00
dependabot[bot]
e5eb9bf4f2 build(deps-dev): bump types-psutil from 5.8.19 to 5.8.20
Bumps [types-psutil](https://github.com/python/typeshed) from 5.8.19 to 5.8.20.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-31 18:53:45 +00:00
Willi Ballenthin
a3615ad0d3 Merge pull request #884 from mandiant/dependabot/pip/types-requests-2.27.8
build(deps-dev): bump types-requests from 2.27.7 to 2.27.8
2022-01-31 11:53:32 -07:00
Willi Ballenthin
2f6b5566d8 Merge pull request #883 from mandiant/dependabot/pip/types-pyyaml-6.0.4
build(deps-dev): bump types-pyyaml from 6.0.3 to 6.0.4
2022-01-31 11:53:20 -07:00
dependabot[bot]
79b40cab14 build(deps-dev): bump black from 21.12b0 to 22.1.0
Bumps [black](https://github.com/psf/black) from 21.12b0 to 22.1.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/commits/22.1.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-31 18:53:12 +00:00
Willi Ballenthin
6276b5d79e Merge pull request #882 from mandiant/dependabot/pip/smda-1.7.0
build(deps): bump smda from 1.6.2 to 1.7.0
2022-01-31 11:52:52 -07:00
dependabot[bot]
fac7ec1e00 build(deps-dev): bump types-requests from 2.27.7 to 2.27.8
Bumps [types-requests](https://github.com/python/typeshed) from 2.27.7 to 2.27.8.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-31 14:15:22 +00:00
dependabot[bot]
356e5babd0 build(deps-dev): bump types-pyyaml from 6.0.3 to 6.0.4
Bumps [types-pyyaml](https://github.com/python/typeshed) from 6.0.3 to 6.0.4.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-31 14:15:20 +00:00
dependabot[bot]
b2de090581 build(deps): bump smda from 1.6.2 to 1.7.0
Bumps [smda](https://github.com/danielplohmann/smda) from 1.6.2 to 1.7.0.
- [Release notes](https://github.com/danielplohmann/smda/releases)
- [Commits](https://github.com/danielplohmann/smda/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-31 14:15:18 +00:00
Willi Ballenthin
364ec1fa2c Merge pull request #880 from mandiant/dependabot/pip/types-requests-2.27.7
build(deps-dev): bump types-requests from 2.27.3 to 2.27.7
2022-01-26 08:50:16 -07:00
Willi Ballenthin
afc64b8287 Merge branch 'master' into dependabot/pip/types-requests-2.27.7 2022-01-26 08:50:08 -07:00
dependabot[bot]
5953f86c7e build(deps-dev): bump types-requests from 2.27.3 to 2.27.7
Bumps [types-requests](https://github.com/python/typeshed) from 2.27.3 to 2.27.7.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-26 09:33:05 +00:00
Moritz
cfad012f92 Merge pull request #875 from kn0wl3dge/feature/103-validate_attck_mbc
Validate ATT&CK/MBC categories and IDs
2022-01-26 10:32:44 +01:00
Baptistin Boilot
2e8c2f40d6 linter: update linter-data.json with mitre att&ck references only 2022-01-26 00:11:01 +01:00
Baptistin Boilot
377c805fe7 linter: improve linter-data.json opening and add documentation
- Open linter-data.json in byte mode
- Add a comment explaining how to invoke the script
2022-01-24 22:48:59 +01:00
Capa Bot
bbb97da3fc Sync capa rules submodule 2022-01-24 17:10:29 +00:00
Capa Bot
78fde6f812 Sync capa rules submodule 2022-01-24 16:57:32 +00:00
Capa Bot
09081c0d2d Sync capa rules submodule 2022-01-24 16:51:22 +00:00
Willi Ballenthin
abeb507ea0 Merge pull request #876 from mandiant/dependabot/pip/types-colorama-0.4.7
build(deps-dev): bump types-colorama from 0.4.6 to 0.4.7
2022-01-24 09:49:41 -07:00
dependabot[bot]
d8c2759a72 build(deps-dev): bump types-colorama from 0.4.6 to 0.4.7
Bumps [types-colorama](https://github.com/python/typeshed) from 0.4.6 to 0.4.7.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-24 14:12:06 +00:00
Capa Bot
f0fc39e1d0 Sync capa-testfiles submodule 2022-01-24 13:37:25 +00:00
Capa Bot
81d604d85a Sync capa-testfiles submodule 2022-01-24 11:00:44 +00:00
Baptistin Boilot
0c978a8def scripts: fix typing issue in setup-linter-dependencies 2022-01-22 17:18:02 +01:00
Baptistin Boilot
c6ac239c5a linter: fix imports and codingstyle 2022-01-22 16:45:50 +01:00
Baptistin Boilot
370ad6cdd7 docs: add code documentation and update changelog 2022-01-22 16:45:49 +01:00
Baptistin Boilot
2bcd725e04 linter: add the possibility to enable or disable mbc and att&ck linting 2022-01-22 16:45:47 +01:00
Baptistin Boilot
0b487546bb linter: add mbc data extractor and linter 2022-01-22 16:45:46 +01:00
Baptistin Boilot
67d8d832c9 linter: refactor att&ck linter and add attck json data 2022-01-22 16:45:35 +01:00
Baptistin Boilot
fa99782f02 linter: add a linter rule that checks for invalid att&ck technique 2022-01-22 16:44:07 +01:00
Baptistin Boilot
60a30518bc linter: add mitre att&ck ttps extraction script 2022-01-22 16:43:42 +01:00
dependabot[bot]
122fb5f9f1 build(deps-dev): bump types-termcolor from 1.1.2 to 1.1.3
Bumps [types-termcolor](https://github.com/python/typeshed) from 1.1.2 to 1.1.3.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-22 16:43:37 +01:00
Willi Ballenthin
5daf444c8a Merge pull request #871 from mandiant/dependabot/pip/types-termcolor-1.1.3
build(deps-dev): bump types-termcolor from 1.1.2 to 1.1.3
2022-01-17 14:02:51 -07:00
Willi Ballenthin
41fbb8cdc4 Merge pull request #872 from mandiant/dependabot/pip/types-colorama-0.4.6
build(deps-dev): bump types-colorama from 0.4.5 to 0.4.6
2022-01-17 14:02:37 -07:00
dependabot[bot]
edfb69f8e9 build(deps-dev): bump types-colorama from 0.4.5 to 0.4.6
Bumps [types-colorama](https://github.com/python/typeshed) from 0.4.5 to 0.4.6.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-17 14:18:10 +00:00
dependabot[bot]
14b0d8e7a6 build(deps-dev): bump types-termcolor from 1.1.2 to 1.1.3
Bumps [types-termcolor](https://github.com/python/typeshed) from 1.1.2 to 1.1.3.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-17 14:18:07 +00:00
Willi Ballenthin
a085ff855d Merge pull request #868 from mandiant/fix/867
elf: parse section headers looking for Linux notes, too
2022-01-14 11:41:22 -07:00
William Ballenthin
b392b48b28 black 2022-01-13 15:24:58 -07:00
William Ballenthin
93355a6884 changelog 2022-01-13 15:23:17 -07:00
William Ballenthin
b28b30eb0f elf: parse section headers looking for Linux notes, too
closes #867
2022-01-13 15:21:23 -07:00
Willi Ballenthin
c0851fc643 Merge pull request #863 from mandiant/v3.1.0
version: v3.1.0
2022-01-12 14:18:22 -07:00
Willi Ballenthin
de7592b351 changelog: add additional contributor 2022-01-11 14:29:15 -07:00
Willi Ballenthin
5530bbad53 Update CHANGELOG.md
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2022-01-11 14:28:17 -07:00
Willi Ballenthin
4f0067e408 Update CHANGELOG.md
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2022-01-11 14:27:59 -07:00
Willi Ballenthin
b444c28a19 changelog: fix format 2022-01-11 10:05:40 -07:00
Willi Ballenthin
a4cc409c95 Update capa/version.py
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2022-01-10 12:39:07 -07:00
Moritz
fcb08501c0 Merge pull request #865 from mandiant/mr-tz-patch-1
Update global_.py
2022-01-10 19:21:24 +01:00
Moritz
cb2d00cefc Update global_.py 2022-01-10 19:04:52 +01:00
Willi Ballenthin
1cb9fc8a40 Merge pull request #864 from doomedraven/patch-1
Fix deprication warning from IDA
2022-01-10 10:52:10 -07:00
doomedraven
85cfc04bdb Fix deprication warning from IDA
```
    if info.procName == "metapc" and info.is_64bit():
```
Please use "procname" instead of "procName" ("procName" is kept for backward-compatibility, and will be removed soon.)
2022-01-10 18:37:59 +01:00
Willi Ballenthin
6555a3604f changelog: intro section 2022-01-10 09:49:00 -07:00
Willi Ballenthin
a97262d022 changelog: v3.1.0 2022-01-10 09:39:46 -07:00
Willi Ballenthin
8ad54271e9 version: v3.1.0 2022-01-10 09:33:39 -07:00
Willi Ballenthin
e5b9a20d09 changelog: add rule changes and contributors 2022-01-10 09:32:49 -07:00
Willi Ballenthin
0d37d182ea changelog: add some additional entries 2022-01-10 09:26:14 -07:00
Willi Ballenthin
6690634a3f Merge pull request #858 from mandiant/dependabot/pip/types-pyyaml-6.0.3
build(deps-dev): bump types-pyyaml from 6.0.1 to 6.0.3
2022-01-10 08:26:25 -07:00
dependabot[bot]
8f3730bae3 build(deps-dev): bump types-pyyaml from 6.0.1 to 6.0.3
Bumps [types-pyyaml](https://github.com/python/typeshed) from 6.0.1 to 6.0.3.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-10 15:25:42 +00:00
Willi Ballenthin
8f4e726774 Merge pull request #859 from mandiant/dependabot/pip/types-tabulate-0.8.5
build(deps-dev): bump types-tabulate from 0.8.4 to 0.8.5
2022-01-10 08:25:12 -07:00
Willi Ballenthin
5b8eda0f08 Merge pull request #861 from mandiant/dependabot/pip/mypy-0.931
build(deps-dev): bump mypy from 0.930 to 0.931
2022-01-10 08:24:59 -07:00
Willi Ballenthin
f5f62bbd71 Merge pull request #862 from mandiant/dependabot/pip/types-psutil-5.8.19
build(deps-dev): bump types-psutil from 5.8.17 to 5.8.19
2022-01-10 08:24:41 -07:00
dependabot[bot]
24c3edc7ec build(deps-dev): bump types-psutil from 5.8.17 to 5.8.19
Bumps [types-psutil](https://github.com/python/typeshed) from 5.8.17 to 5.8.19.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-10 14:18:21 +00:00
dependabot[bot]
0e3d46ef5e build(deps-dev): bump mypy from 0.930 to 0.931
Bumps [mypy](https://github.com/python/mypy) from 0.930 to 0.931.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v0.930...v0.931)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-10 14:18:19 +00:00
dependabot[bot]
a3546b65f7 build(deps-dev): bump types-tabulate from 0.8.4 to 0.8.5
Bumps [types-tabulate](https://github.com/python/typeshed) from 0.8.4 to 0.8.5.
- [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>
2022-01-10 14:18:09 +00:00
Willi Ballenthin
01b694b6ab Merge pull request #851 from kn0wl3dge/fix/430
smda: fix negative number extraction
2022-01-03 12:08:41 -07:00
Moritz
3598f83091 Merge pull request #856 from mandiant/dependabot/pip/psutil-5.9.0
build(deps-dev): bump psutil from 5.8.0 to 5.9.0
2022-01-03 17:33:56 +01:00
Moritz
2085dd7b02 Merge pull request #853 from mandiant/dependabot/pip/ruamel-yaml-0.17.20
build(deps): bump ruamel-yaml from 0.17.19 to 0.17.20
2022-01-03 17:33:40 +01:00
Moritz
65d916332d Merge pull request #855 from mandiant/dependabot/pip/types-psutil-5.8.17
build(deps-dev): bump types-psutil from 5.8.16 to 5.8.17
2022-01-03 17:33:26 +01:00
Moritz
1937efce88 Merge pull request #852 from mandiant/dependabot/pip/types-tabulate-0.8.4
build(deps-dev): bump types-tabulate from 0.8.3 to 0.8.4
2022-01-03 17:33:19 +01:00
Moritz
501d607b3a Merge pull request #854 from mandiant/dependabot/pip/types-colorama-0.4.5
build(deps-dev): bump types-colorama from 0.4.4 to 0.4.5
2022-01-03 17:33:07 +01:00
dependabot[bot]
7d6670c59e build(deps-dev): bump psutil from 5.8.0 to 5.9.0
Bumps [psutil](https://github.com/giampaolo/psutil) from 5.8.0 to 5.9.0.
- [Release notes](https://github.com/giampaolo/psutil/releases)
- [Changelog](https://github.com/giampaolo/psutil/blob/master/HISTORY.rst)
- [Commits](https://github.com/giampaolo/psutil/compare/release-5.8.0...release-5.9.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-03 14:11:03 +00:00
dependabot[bot]
fe608db16a build(deps-dev): bump types-psutil from 5.8.16 to 5.8.17
Bumps [types-psutil](https://github.com/python/typeshed) from 5.8.16 to 5.8.17.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-03 14:10:58 +00:00
dependabot[bot]
be1f313d57 build(deps-dev): bump types-colorama from 0.4.4 to 0.4.5
Bumps [types-colorama](https://github.com/python/typeshed) from 0.4.4 to 0.4.5.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-03 14:10:55 +00:00
dependabot[bot]
cb77c55d2c build(deps): bump ruamel-yaml from 0.17.19 to 0.17.20
Bumps [ruamel-yaml](https://sourceforge.net/p/ruamel-yaml/code/ci/default/tree) from 0.17.19 to 0.17.20.

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-03 14:10:53 +00:00
dependabot[bot]
417aa35c60 build(deps-dev): bump types-tabulate from 0.8.3 to 0.8.4
Bumps [types-tabulate](https://github.com/python/typeshed) from 0.8.3 to 0.8.4.
- [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>
2022-01-03 14:10:46 +00:00
Baptistin Boilot
18877eb676 changelog: add fixed issue 2021-12-31 21:14:56 +01:00
Baptistin Boilot
a9670c9510 smda: fix number extractor to return only unsigned values
SmdaInstruction operands are python `str` objects. SMDA number operands are signed integers.
This commit adds a converter to the SMDA number extractor.
The goal is to convert any signed number to the two’s complement representation with the correct bitness.
2021-12-31 20:10:36 +01:00
Baptistin Boilot
8474369575 tests: add fixtures for two's complement numbers
Add fixtures to validate the following number features:
- number(0x0): to check feature extraction for null number
- number(0xFFFFFFFF): to check feature extraction for -1 number
- number(0xFFFFFFF0): to check feature extraction for negative number (-0x10 in this case)
2021-12-31 20:08:56 +01:00
Baptistin Boilot
4739d121a2 scripts: add backend parameter (-b) to show-features.py 2021-12-31 20:07:34 +01:00
Mike Hunhoff
e47f5a2548 Merge pull request #849 from mandiant/fix/845
capa explorer: updating supported IDA versions
2021-12-31 10:48:53 -07:00
Willi Ballenthin
51f5628383 Merge pull request #847 from mandiant/dependabot/pip/ruamel-yaml-0.17.19
build(deps): bump ruamel-yaml from 0.17.17 to 0.17.19
2021-12-29 09:44:24 -07:00
Willi Ballenthin
aa67a1b285 Merge pull request #846 from mandiant/dependabot/pip/types-psutil-5.8.16
build(deps-dev): bump types-psutil from 5.8.15 to 5.8.16
2021-12-29 09:44:15 -07:00
Willi Ballenthin
d22e51fd84 Merge pull request #848 from mandiant/dependabot/pip/mypy-0.930
build(deps-dev): bump mypy from 0.920 to 0.930
2021-12-29 09:42:21 -07:00
Michael Hunhoff
cde4af40fe capa explorer: updating supported IDA versions 2021-12-28 10:51:53 -07:00
dependabot[bot]
a147755d13 build(deps-dev): bump mypy from 0.920 to 0.930
Bumps [mypy](https://github.com/python/mypy) from 0.920 to 0.930.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v0.920...v0.930)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-27 14:12:16 +00:00
dependabot[bot]
7b6c293069 build(deps): bump ruamel-yaml from 0.17.17 to 0.17.19
Bumps [ruamel-yaml](https://sourceforge.net/p/ruamel-yaml/code/ci/default/tree) from 0.17.17 to 0.17.19.

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-27 14:12:12 +00:00
dependabot[bot]
b3f1244641 build(deps-dev): bump types-psutil from 5.8.15 to 5.8.16
Bumps [types-psutil](https://github.com/python/typeshed) from 5.8.15 to 5.8.16.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-27 14:12:06 +00:00
Capa Bot
e6423700b9 Sync capa rules submodule 2021-12-23 16:34:46 +00:00
Moritz
9462a26a05 Merge pull request #844 from mandiant/dependabot/pip/mypy-0.920
build(deps-dev): bump mypy from 0.910 to 0.920
2021-12-20 16:31:41 +01:00
dependabot[bot]
c059a52d0e build(deps-dev): bump mypy from 0.910 to 0.920
Bumps [mypy](https://github.com/python/mypy) from 0.910 to 0.920.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v0.910...v0.920)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-20 14:09:06 +00:00
Capa Bot
a221db8a59 Sync capa rules submodule 2021-12-20 12:48:22 +00:00
Moritz
df43ed0219 Merge pull request #842 from mandiant/fix/maec-mal-fam
support maec/malware-family meta
2021-12-20 13:15:50 +01:00
Capa Bot
90430f52c6 Sync capa-testfiles submodule 2021-12-15 15:33:39 +00:00
Moritz Raabe
4e7f0b4591 support maec/malware-family meta 2021-12-15 10:40:34 +01:00
Capa Bot
bda76c22ec Sync capa rules submodule 2021-12-14 21:52:49 +00:00
Capa Bot
d67223c321 Sync capa rules submodule 2021-12-14 21:46:38 +00:00
Capa Bot
21278ff595 Sync capa rules submodule 2021-12-14 21:45:58 +00:00
Capa Bot
21fd6b27e2 Sync capa rules submodule 2021-12-13 18:48:16 +00:00
Capa Bot
cc8d57b242 Sync capa-testfiles submodule 2021-12-13 17:24:52 +00:00
Capa Bot
6081f4573c Sync capa-testfiles submodule 2021-12-13 17:24:32 +00:00
Capa Bot
ea2cafa715 Sync capa-testfiles submodule 2021-12-13 17:24:02 +00:00
Capa Bot
a34c993e31 Sync capa rules submodule 2021-12-07 04:32:49 +00:00
Willi Ballenthin
1a5fc3a21a Merge pull request #839 from cl3o/master
types: Add assert_never for exhaustivenes checking with mypy
2021-12-06 13:55:41 -07:00
cl3o
c15a9a72f5 Add local variable for easy_rules_by_feature at the beginning of match 2021-12-06 20:55:15 +01:00
cl3o
5b35058338 Forgot to add the second fix to the first commit. 2021-12-06 20:32:44 +01:00
cl3o
a0ca6e18c8 Made proposed changes to fix mypy errors 2021-12-06 20:30:07 +01:00
Capa Bot
1917004292 Sync capa rules submodule 2021-12-06 19:22:59 +00:00
Capa Bot
8ee3bb08bc Sync capa rules submodule 2021-12-06 18:24:54 +00:00
Capa Bot
7e96059fb5 Sync capa rules submodule 2021-12-06 17:58:59 +00:00
Capa Bot
4f7f06d316 Sync capa rules submodule 2021-12-06 17:57:11 +00:00
Capa Bot
448b5392be Sync capa rules submodule 2021-12-06 17:56:26 +00:00
Willi Ballenthin
6f5f3e091a Merge pull request #840 from mandiant/dependabot/pip/black-21.12b0
build(deps-dev): bump black from 21.11b1 to 21.12b0
2021-12-06 10:45:51 -07:00
dependabot[bot]
fa6a2069ce build(deps-dev): bump black from 21.11b1 to 21.12b0
Bumps [black](https://github.com/psf/black) from 21.11b1 to 21.12b0.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-06 14:12:23 +00:00
Capa Bot
09fd371b9d Sync capa-testfiles submodule 2021-12-06 10:13:41 +00:00
Capa Bot
a598745938 Sync capa-testfiles submodule 2021-12-06 10:06:57 +00:00
Capa Bot
7751f693c8 Sync capa-testfiles submodule 2021-12-06 10:02:45 +00:00
Capa Bot
7ade9ca43e Sync capa-testfiles submodule 2021-12-06 10:01:17 +00:00
cl3o
061a66e437 create function assert_never 2021-12-04 19:02:54 +01:00
Capa Bot
39536e2727 Sync capa rules submodule 2021-12-03 15:29:51 +00:00
Capa Bot
38038626d4 Sync capa rules submodule 2021-12-03 15:29:28 +00:00
Capa Bot
c3d34abe89 Sync capa-testfiles submodule 2021-12-03 12:12:30 +00:00
Capa Bot
baf5005998 Sync capa-testfiles submodule 2021-12-03 12:12:20 +00:00
Capa Bot
107c3c0cf9 Sync capa rules submodule 2021-11-30 22:06:21 +00:00
Capa Bot
2d1bd37816 Sync capa rules submodule 2021-11-30 15:24:28 +00:00
Capa Bot
de017b15d0 Sync capa-testfiles submodule 2021-11-30 15:24:09 +00:00
Capa Bot
3b0974ae3e Sync capa rules submodule 2021-11-29 23:46:52 +00:00
Willi Ballenthin
cf6cbc16df Merge pull request #838 from mandiant/dependabot/pip/types-psutil-5.8.15
build(deps-dev): bump types-psutil from 5.8.14 to 5.8.15
2021-11-29 08:47:44 -07:00
dependabot[bot]
bd60a8d9cd build(deps-dev): bump types-psutil from 5.8.14 to 5.8.15
Bumps [types-psutil](https://github.com/python/typeshed) from 5.8.14 to 5.8.15.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-29 14:09:09 +00:00
Capa Bot
c77240c6b4 Sync capa rules submodule 2021-11-26 16:21:34 +00:00
Moritz
14d803c604 Merge pull request #837 from mandiant/dependabot/pip/black-21.11b1
build(deps-dev): bump black from 21.10b0 to 21.11b1
2021-11-22 18:45:02 +01:00
dependabot[bot]
f764829ca9 build(deps-dev): bump black from 21.10b0 to 21.11b1
Bumps [black](https://github.com/psf/black) from 21.10b0 to 21.11b1.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-22 14:10:19 +00:00
Willi Ballenthin
418eedd7bd freeze: fix doc describing format 2021-11-17 12:06:56 -07:00
Willi Ballenthin
b9f1fe56c8 Merge pull request #834 from mandiant/williballenthin-patch-1
setup: bump viv-utils to v0.6.9
2021-11-16 11:21:30 -07:00
Willi Ballenthin
7e50a957ff ci: tests: python versions are strings not floats 2021-11-16 10:12:34 -07:00
Willi Ballenthin
137cff6127 ci: tests: test under py3.10 too 2021-11-16 10:06:32 -07:00
Willi Ballenthin
807b99e5e5 changelog 2021-11-15 14:12:07 -07:00
Willi Ballenthin
e21c69f4e3 setup: bump viv-utils to v0.6.9
closes #816 
closes #683
2021-11-15 14:10:48 -07:00
Moritz
9f7daca86e Merge pull request #833 from mandiant/dependabot/pip/types-pyyaml-6.0.1
build(deps-dev): bump types-pyyaml from 6.0.0 to 6.0.1
2021-11-15 16:54:11 +01:00
Moritz
1b89e274c9 Merge pull request #832 from mandiant/dependabot/pip/isort-5.10.1
build(deps-dev): bump isort from 5.10.0 to 5.10.1
2021-11-15 16:54:02 +01:00
Moritz
dd768dc080 Merge pull request #831 from mandiant/dependabot/pip/viv-utils-flirt--0.6.8
build(deps): bump viv-utils[flirt] from 0.6.7 to 0.6.8
2021-11-15 16:53:53 +01:00
dependabot[bot]
4aea481967 build(deps-dev): bump types-pyyaml from 6.0.0 to 6.0.1
Bumps [types-pyyaml](https://github.com/python/typeshed) from 6.0.0 to 6.0.1.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-15 14:12:07 +00:00
dependabot[bot]
265629d127 build(deps-dev): bump isort from 5.10.0 to 5.10.1
Bumps [isort](https://github.com/pycqa/isort) from 5.10.0 to 5.10.1.
- [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.10.0...5.10.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-15 14:12:04 +00:00
dependabot[bot]
cef0cb809f build(deps): bump viv-utils[flirt] from 0.6.7 to 0.6.8
Bumps [viv-utils[flirt]](https://github.com/williballenthin/viv-utils) from 0.6.7 to 0.6.8.
- [Release notes](https://github.com/williballenthin/viv-utils/releases)
- [Commits](https://github.com/williballenthin/viv-utils/compare/v0.6.7...v0.6.8)

---
updated-dependencies:
- dependency-name: viv-utils[flirt]
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-15 14:11:59 +00:00
Willi Ballenthin
57fe1e27b6 Merge pull request #830 from mandiant/perf/rule-selection
perf: don't try to match rules that will never match
2021-11-12 11:54:29 -07:00
Willi Ballenthin
83253eb7d0 rules: better variable name 2021-11-12 11:53:03 -07:00
Willi Ballenthin
9b5e8ff45d Update capa/rules.py
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2021-11-12 11:51:39 -07:00
William Ballenthin
cdfacc6247 Merge branch 'master' of github.com:fireeye/capa into perf/rule-selection 2021-11-10 14:30:08 -07:00
Capa Bot
10d747cc8c Sync capa rules submodule 2021-11-10 21:29:25 +00:00
William Ballenthin
a6b366602c mypy 2021-11-10 14:21:28 -07:00
William Ballenthin
80fb9dec3c pep8 2021-11-10 14:15:52 -07:00
William Ballenthin
68c86cf620 rules: easy/hard: better detect edge cases in optional, some, and range 2021-11-10 14:13:57 -07:00
William Ballenthin
e550d48bcd linter: optional maps to some, not range 2021-11-10 14:13:37 -07:00
William Ballenthin
1aaaa8919c rules: easy/hard: simplify indexing by considering not: hard 2021-11-10 13:55:34 -07:00
William Ballenthin
72c2ffc40b linter: add checks for not and optional not under and 2021-11-10 13:47:30 -07:00
William Ballenthin
f7ab2fb13a rules: easy/hard rules: detect not/optional at the root 2021-11-10 13:36:10 -07:00
William Ballenthin
3a1272246f rules: code consistency 2021-11-10 13:36:00 -07:00
William Ballenthin
6039a33bf8 engine: remove old import 2021-11-10 12:56:40 -07:00
William Ballenthin
2d68fb2536 pep8 2021-11-10 12:51:27 -07:00
William Ballenthin
845df282ef tests: split out match tests and validate alternative algorithms 2021-11-10 12:44:58 -07:00
William Ballenthin
1406dc28d9 rules: ruleset: fix collection of features under not statements 2021-11-10 12:44:19 -07:00
William Ballenthin
67884dd255 rules: match: more documentation 2021-11-09 16:42:32 -07:00
William Ballenthin
2bf05ac631 rules: index easy/hard: better handle not: statements 2021-11-09 16:37:30 -07:00
William Ballenthin
8cb04e4737 Merge branch 'master' into perf/rule-selection 2021-11-09 16:28:03 -07:00
William Ballenthin
733126591e Merge branch 'perf/query-optimizer' 2021-11-09 16:27:09 -07:00
William Ballenthin
d4d801c246 optimizer: tweak costs slightly 2021-11-09 16:26:26 -07:00
Willi Ballenthin
84ba32a8fe Merge pull request #829 from mandiant/perf/query-optimizer
perf: add query optimizer
2021-11-09 16:25:22 -07:00
William Ballenthin
ea386d02b6 tests: add test demonstrating optimizer 2021-11-09 16:24:26 -07:00
William Ballenthin
77cac63443 Merge branch 'master' into perf/query-optimizer 2021-11-09 16:12:30 -07:00
Willi Ballenthin
9350ee9479 Merge pull request #827 from mandiant/perf/short-circuit
perf: short circuit logic nodes when appropriate
2021-11-09 16:10:20 -07:00
Willi Ballenthin
025d156068 Merge pull request #828 from mandiant/profiling
profile infrastructure
2021-11-09 16:09:34 -07:00
William Ballenthin
7a4aee592b profile-time: add doc 2021-11-09 16:08:39 -07:00
Willi Ballenthin
f427c5e961 Update capa/engine.py
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2021-11-09 10:49:10 -07:00
Willi Ballenthin
51af2d4a56 Update capa/engine.py
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2021-11-09 10:49:01 -07:00
Willi Ballenthin
a68812b223 Update capa/engine.py
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2021-11-09 10:48:54 -07:00
William Ballenthin
e05f8c7034 changelog 2021-11-09 10:27:33 -07:00
William Ballenthin
182377581a main: use ruleset.match instead of engine.mathc 2021-11-09 09:52:45 -07:00
William Ballenthin
e647ae2ac4 rules: ruleset: add optimized match routine 2021-11-09 09:52:32 -07:00
William Ballenthin
1311da99ff rules: make Scope an enum 2021-11-09 09:51:50 -07:00
William Ballenthin
8badf226a2 engine: document match routine 2021-11-09 09:51:18 -07:00
William Ballenthin
6909d6a541 changelog 2021-11-08 16:04:15 -07:00
William Ballenthin
e287dc9a32 optimizer: fix sort order 2021-11-08 15:54:14 -07:00
William Ballenthin
152d0f3244 ruleset: add query optimizer 2021-11-08 15:34:59 -07:00
William Ballenthin
a6e2cfc90a Merge branch 'profiling' into perf/short-circuit 2021-11-08 15:24:50 -07:00
William Ballenthin
18c30e4f12 main: remove perf debug msgs 2021-11-08 15:24:43 -07:00
William Ballenthin
3c4f4d302c Merge branch 'profiling' into perf/short-circuit 2021-11-08 15:23:23 -07:00
William Ballenthin
2abebfbce7 main: remove perf messages 2021-11-08 15:22:58 -07:00
William Ballenthin
0b517c51d8 main: remove perf messages 2021-11-08 15:22:01 -07:00
William Ballenthin
9fbbda11b8 Merge branch 'profiling' into perf/short-circuit 2021-11-08 15:20:22 -07:00
William Ballenthin
6f6831f812 perf: document that counters is unstable 2021-11-08 15:20:11 -07:00
William Ballenthin
d425bb31c4 Merge branch 'profiling' into perf/short-circuit 2021-11-08 15:16:22 -07:00
William Ballenthin
334425a08f changelog 2021-11-08 15:16:08 -07:00
William Ballenthin
3e74da96a6 engine: make short circuiting configurable 2021-11-08 14:55:11 -07:00
William Ballenthin
ad119d789b Merge branch 'profiling' into perf/short-circuit 2021-11-08 14:35:26 -07:00
William Ballenthin
6c8d246af9 fix bad merge 2021-11-08 14:31:43 -07:00
William Ballenthin
26b7a0b91d Merge branch 'master' into profiling 2021-11-08 14:29:40 -07:00
Willi Ballenthin
0b6c6227b9 Merge pull request #825 from mandiant/fix/circular-import-freeze
fix circular import freeze
2021-11-08 14:28:01 -07:00
William Ballenthin
94fd7673fd common: mypy 2021-11-08 14:27:44 -07:00
William Ballenthin
f598acb8fc scripts: remove old profiling scripts 2021-11-08 14:24:48 -07:00
William Ballenthin
b621205a06 mypy 2021-11-08 14:24:13 -07:00
William Ballenthin
9fa9c6a5d0 tests: add test demonstrating short circuiting 2021-11-08 14:07:44 -07:00
William Ballenthin
1a84051679 changelog 2021-11-08 14:07:31 -07:00
William Ballenthin
d987719889 engine: some: correctly count satisfied children 2021-11-08 13:53:37 -07:00
William Ballenthin
96813c37b7 remove old improt 2021-11-08 13:48:33 -07:00
William Ballenthin
70f007525d pep8 2021-11-08 12:11:01 -07:00
William Ballenthin
e3496b0660 engine: move optimizer into its own module 2021-11-08 12:10:22 -07:00
William Ballenthin
24b4c99635 changelog 2021-11-08 11:58:02 -07:00
William Ballenthin
27b4a8ba73 common: remove old import 2021-11-08 11:55:58 -07:00
William Ballenthin
51b3f38f55 common: move Result to capa.common from capa.engine
fixes circular import error in capa.features.freeze
2021-11-08 11:54:36 -07:00
William Ballenthin
a35be4a666 scripts: add py script for profiling time 2021-11-08 11:52:34 -07:00
William Ballenthin
5770d0c12d perf: add reset routine 2021-11-08 11:52:25 -07:00
William Ballenthin
0629c584e1 common: move Result to capa.common from capa.engine
fixes circular import error in capa.features.freeze
2021-11-08 11:52:13 -07:00
William Ballenthin
480df323e5 scripts: add py script for profiling time 2021-11-08 11:51:09 -07:00
William Ballenthin
a995b53c38 perf: add reset routine 2021-11-08 11:50:49 -07:00
William Ballenthin
35fa50dbee pep8 2021-11-08 11:50:37 -07:00
William Ballenthin
d86c3f4d48 common: move Result to capa.common from capa.engine
fixes circular import error in capa.features.freeze
2021-11-08 11:50:16 -07:00
Moritz
4696c0ebb6 Merge pull request #822 from mandiant/dependabot/pip/types-psutil-5.8.14
build(deps-dev): bump types-psutil from 5.8.13 to 5.8.14
2021-11-08 17:02:58 +01:00
Moritz
09724e9787 Merge pull request #823 from mandiant/dependabot/pip/isort-5.10.0
build(deps-dev): bump isort from 5.9.3 to 5.10.0
2021-11-08 17:02:33 +01:00
dependabot[bot]
636548cdec build(deps-dev): bump isort from 5.9.3 to 5.10.0
Bumps [isort](https://github.com/pycqa/isort) from 5.9.3 to 5.10.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.9.3...5.10.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-08 14:16:28 +00:00
dependabot[bot]
b3970808df build(deps-dev): bump types-psutil from 5.8.13 to 5.8.14
Bumps [types-psutil](https://github.com/python/typeshed) from 5.8.13 to 5.8.14.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-08 14:16:15 +00:00
William Ballenthin
d573b83c94 rule: optimization: add some documentation 2021-11-05 16:49:38 -06:00
William Ballenthin
e63f072e40 rules: optimizer: use recursive cost of statements 2021-11-05 16:39:00 -06:00
William Ballenthin
a329147d28 engine: some: short circuit 2021-11-05 16:32:23 -06:00
William Ballenthin
18ba986eba engine: or: short circuit 2021-11-05 16:32:12 -06:00
William Ballenthin
8d9f418b2b rules: optimize by cost 2021-11-05 16:20:22 -06:00
William Ballenthin
623bac1a40 engine: statement: document that the order of children is important 2021-11-05 16:19:16 -06:00
William Ballenthin
702d00da91 gitignore 2021-11-05 15:24:24 -06:00
William Ballenthin
3a12472be8 perf: render: show evaluate.feature counter 2021-11-05 15:23:34 -06:00
William Ballenthin
6524449ad1 main: perf: human format the numbers 2021-11-05 15:23:22 -06:00
William Ballenthin
86cab26a69 add perf counters in module capa.perf 2021-11-05 14:59:22 -06:00
William Ballenthin
3d068fe3cd scripts: add utilities for collecting profile traces 2021-11-04 13:17:38 -06:00
William Ballenthin
f98236046b main: add coarse timing measurements 2021-11-04 12:38:35 -06:00
William Ballenthin
ed3bd4ef75 main: add timing ctx manager 2021-11-04 12:20:05 -06:00
Capa Bot
7d3ae7a91b Sync capa rules submodule 2021-11-03 18:29:09 +00:00
Capa Bot
0409c431b8 Sync capa rules submodule 2021-11-02 18:47:47 +00:00
Capa Bot
ffbb841b03 Sync capa rules submodule 2021-11-02 18:47:18 +00:00
Willi Ballenthin
e9a7dbc2ff Merge pull request #820 from mandiant/fix/linter-file-format
auto recognize shellcode based on file extension
2021-11-02 11:31:33 -06:00
Capa Bot
10dc8950c1 Sync capa rules submodule 2021-11-02 17:29:30 +00:00
Capa Bot
fe0fb1ccd2 Sync capa rules submodule 2021-11-02 17:17:47 +00:00
Moritz Raabe
e9170a1d4b auto recognize shellcode based on file extension 2021-11-02 18:02:37 +01:00
Capa Bot
02bd8581d8 Sync capa-testfiles submodule 2021-11-02 16:42:40 +00:00
Moritz
ca574201a4 Merge pull request #818 from mandiant/dependabot/pip/ruamel-yaml-0.17.17
build(deps): bump ruamel-yaml from 0.17.16 to 0.17.17
2021-11-02 17:36:03 +01:00
Moritz
8e744d94e6 Merge pull request #817 from mandiant/dependabot/pip/black-21.10b0
build(deps-dev): bump black from 21.9b0 to 21.10b0
2021-11-02 17:35:52 +01:00
dependabot[bot]
6a28330dd1 build(deps): bump ruamel-yaml from 0.17.16 to 0.17.17
Bumps [ruamel-yaml](https://sourceforge.net/p/ruamel-yaml/code/ci/default/tree) from 0.17.16 to 0.17.17.

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-01 14:11:49 +00:00
dependabot[bot]
4537b52c18 build(deps-dev): bump black from 21.9b0 to 21.10b0
Bumps [black](https://github.com/psf/black) from 21.9b0 to 21.10b0.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-01 14:11:42 +00:00
Willi Ballenthin
29e61e24a6 Merge pull request #815 from mandiant/feature-3.0.3
v3.0.3
2021-10-27 10:14:35 -06:00
William Ballenthin
041c8a4c2d changelog 2021-10-27 09:43:35 -06:00
Capa Bot
433dfd8fa9 Sync capa rules submodule 2021-10-27 15:34:46 +00:00
William Ballenthin
2b46043419 v3.0.3 2021-10-27 09:32:45 -06:00
William Ballenthin
d31c8b0190 changelog 2021-10-27 09:29:54 -06:00
Willi Ballenthin
9003fdc1a2 Merge pull request #814 from mandiant/fix-802
bail with unique error codes
2021-10-27 09:25:55 -06:00
William Ballenthin
b1f4a2853e Merge branch 'master' of github.com:fireeye/capa into fix-802 2021-10-27 09:25:29 -06:00
William Ballenthin
07412f047d tests: fix check of status code E_MISSING_FILE 2021-10-27 09:24:22 -06:00
Willi Ballenthin
26ac21b908 Merge pull request #813 from mandiant/fix-130
Fix 130
2021-10-27 09:20:43 -06:00
William Ballenthin
4cc496a8e5 main: use constants to represent error codes 2021-10-26 16:57:33 -06:00
William Ballenthin
4f4e0881b5 changelog 2021-10-26 16:48:02 -06:00
William Ballenthin
9fe164665c main: exit with unique error codes when bailing
TODO: create an enum of all these things so they're easy for a human to
read.

closes #802
2021-10-26 16:46:43 -06:00
William Ballenthin
c74193b5d7 Merge branch 'master' of github.com:fireeye/capa into fix-130 2021-10-26 15:26:22 -06:00
William Ballenthin
31ef06ef2b sync testfiles 2021-10-26 15:26:18 -06:00
Capa Bot
83a95d66d1 Sync capa-testfiles submodule 2021-10-26 21:24:10 +00:00
William Ballenthin
4451b76f89 pep8 2021-10-26 15:21:28 -06:00
William Ballenthin
a1075b63ec tests: add demonstration of bb layout 2021-10-26 15:20:08 -06:00
William Ballenthin
97c41228e0 changelog 2021-10-26 15:10:50 -06:00
William Ballenthin
8903d2abcb show-capabilities-by-function: also include matches from BBs in fn 2021-10-26 15:05:53 -06:00
William Ballenthin
328e13fbfe main: compute function & bb layout
so bb can be associated with function in output.
only captures BBs that have a rule match,
otherwise, there might be too much data captured.
closes #130.
2021-10-26 15:04:50 -06:00
Capa Bot
b7cd5fec76 Sync capa rules submodule 2021-10-25 19:26:56 +00:00
Willi Ballenthin
6086dbcd84 Merge pull request #812 from mandiant/dependabot/pip/viv-utils-flirt--0.6.7
build(deps): bump viv-utils[flirt] from 0.6.6 to 0.6.7
2021-10-25 09:14:41 -06:00
Willi Ballenthin
5f88e02aa3 Merge pull request #811 from mandiant/dependabot/pip/types-pyyaml-6.0.0
build(deps-dev): bump types-pyyaml from 5.4.12 to 6.0.0
2021-10-25 09:04:56 -06:00
dependabot[bot]
96a4f585cd build(deps): bump viv-utils[flirt] from 0.6.6 to 0.6.7
Bumps [viv-utils[flirt]](https://github.com/williballenthin/viv-utils) from 0.6.6 to 0.6.7.
- [Release notes](https://github.com/williballenthin/viv-utils/releases)
- [Commits](https://github.com/williballenthin/viv-utils/compare/v0.6.6...v0.6.7)

---
updated-dependencies:
- dependency-name: viv-utils[flirt]
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-10-25 14:09:33 +00:00
dependabot[bot]
73ec980e01 build(deps-dev): bump types-pyyaml from 5.4.12 to 6.0.0
Bumps [types-pyyaml](https://github.com/python/typeshed) from 5.4.12 to 6.0.0.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-10-25 14:09:26 +00:00
Capa Bot
e5ed7ce0d3 Sync capa rules submodule 2021-10-25 03:39:00 +00:00
Capa Bot
08a7b8afb7 Sync capa-testfiles submodule 2021-10-24 22:00:33 +00:00
Capa Bot
bb7a588f6b Sync capa rules submodule 2021-10-22 17:23:31 +00:00
Capa Bot
9faa0734c1 Sync capa-testfiles submodule 2021-10-22 17:11:32 +00:00
Capa Bot
cf55b34b4e Sync capa-testfiles submodule 2021-10-22 16:57:10 +00:00
Capa Bot
5881899cc2 Sync capa-testfiles submodule 2021-10-22 16:56:36 +00:00
William Ballenthin
4e64ef8ab3 gitignore 2021-10-22 10:20:14 -06:00
Willi Ballenthin
7e5532ac84 Merge pull request #807 from mandiant/dependabot/pip/types-pyyaml-5.4.12
build(deps-dev): bump types-pyyaml from 5.4.10 to 5.4.12
2021-10-18 13:49:55 -06:00
dependabot[bot]
3d638df08c build(deps-dev): bump types-pyyaml from 5.4.10 to 5.4.12
Bumps [types-pyyaml](https://github.com/python/typeshed) from 5.4.10 to 5.4.12.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-10-18 18:50:32 +00:00
Willi Ballenthin
bf984a38ed Merge pull request #808 from mandiant/dependabot/pip/types-tabulate-0.8.3
build(deps-dev): bump types-tabulate from 0.8.2 to 0.8.3
2021-10-18 12:49:47 -06:00
dependabot[bot]
e68f2ce141 build(deps-dev): bump types-tabulate from 0.8.2 to 0.8.3
Bumps [types-tabulate](https://github.com/python/typeshed) from 0.8.2 to 0.8.3.
- [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>
2021-10-18 18:42:21 +00:00
Willi Ballenthin
d0a3244108 Merge pull request #809 from mandiant/dependabot/pip/types-termcolor-1.1.2
build(deps-dev): bump types-termcolor from 1.1.1 to 1.1.2
2021-10-18 12:41:37 -06:00
dependabot[bot]
d09901d512 build(deps-dev): bump types-termcolor from 1.1.1 to 1.1.2
Bumps [types-termcolor](https://github.com/python/typeshed) from 1.1.1 to 1.1.2.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-10-18 18:26:20 +00:00
Willi Ballenthin
2d46bac351 Merge pull request #810 from mandiant/dependabot/pip/types-psutil-5.8.13
build(deps-dev): bump types-psutil from 5.8.12 to 5.8.13
2021-10-18 12:25:22 -06:00
Willi Ballenthin
2285c76cbf Merge pull request #806 from mandiant/dependabot/pip/types-colorama-0.4.4
build(deps-dev): bump types-colorama from 0.4.3 to 0.4.4
2021-10-18 12:25:08 -06:00
dependabot[bot]
c003ab4e42 build(deps-dev): bump types-psutil from 5.8.12 to 5.8.13
Bumps [types-psutil](https://github.com/python/typeshed) from 5.8.12 to 5.8.13.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-10-18 18:24:52 +00:00
Willi Ballenthin
78e97a217a Merge pull request #805 from mandiant/dependabot/pip/pyyaml-6.0
build(deps): bump pyyaml from 5.4.1 to 6.0
2021-10-18 12:24:20 -06:00
dependabot[bot]
720585170c build(deps-dev): bump types-colorama from 0.4.3 to 0.4.4
Bumps [types-colorama](https://github.com/python/typeshed) from 0.4.3 to 0.4.4.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-10-18 14:15:08 +00:00
dependabot[bot]
19d54f3f4d build(deps): bump pyyaml from 5.4.1 to 6.0
Bumps [pyyaml](https://github.com/yaml/pyyaml) from 5.4.1 to 6.0.
- [Release notes](https://github.com/yaml/pyyaml/releases)
- [Changelog](https://github.com/yaml/pyyaml/blob/master/CHANGES)
- [Commits](https://github.com/yaml/pyyaml/compare/5.4.1...6.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-10-18 14:15:05 +00:00
Moritz
23a0aec1e6 Merge pull request #803 from mandiant/dependabot/pip/types-psutil-5.8.12
build(deps-dev): bump types-psutil from 5.8.8 to 5.8.12
2021-10-12 14:22:52 +02:00
Moritz
6b0db01c13 Merge pull request #804 from mandiant/dependabot/pip/pycodestyle-2.8.0
build(deps-dev): bump pycodestyle from 2.7.0 to 2.8.0
2021-10-12 14:22:44 +02:00
dependabot[bot]
93c14c3a1f build(deps-dev): bump pycodestyle from 2.7.0 to 2.8.0
Bumps [pycodestyle](https://github.com/PyCQA/pycodestyle) from 2.7.0 to 2.8.0.
- [Release notes](https://github.com/PyCQA/pycodestyle/releases)
- [Changelog](https://github.com/PyCQA/pycodestyle/blob/main/CHANGES.txt)
- [Commits](https://github.com/PyCQA/pycodestyle/compare/2.7.0...2.8.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-10-11 14:10:44 +00:00
dependabot[bot]
b66760fc5c build(deps-dev): bump types-psutil from 5.8.8 to 5.8.12
Bumps [types-psutil](https://github.com/python/typeshed) from 5.8.8 to 5.8.12.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-10-11 14:10:38 +00:00
Willi Ballenthin
64a801cc55 Merge pull request #801 from mandiant/dependabot/pip/pytest-cov-3.0.0
build(deps-dev): bump pytest-cov from 2.12.1 to 3.0.0
2021-10-04 14:13:43 -06:00
dependabot[bot]
35fc8ee3e8 build(deps-dev): bump pytest-cov from 2.12.1 to 3.0.0
Bumps [pytest-cov](https://github.com/pytest-dev/pytest-cov) from 2.12.1 to 3.0.0.
- [Release notes](https://github.com/pytest-dev/pytest-cov/releases)
- [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-cov/compare/v2.12.1...v3.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-10-04 14:09:30 +00:00
Capa Bot
887c566f7c Sync capa rules submodule 2021-09-30 19:28:13 +00:00
Capa Bot
2f59499087 Sync capa rules submodule 2021-09-30 14:01:54 +00:00
Capa Bot
b4a239569c Sync capa rules submodule 2021-09-30 13:29:23 +00:00
Moritz
e4073a844b Merge pull request #794 from mandiant/go-mandiant
s/fireeye/mandiant
2021-09-30 15:28:53 +02:00
Capa Bot
f313ad37b3 Sync capa-testfiles submodule 2021-09-29 14:54:48 +00:00
Moritz Raabe
8de69c639a s/fireeye/mandiant 2021-09-29 12:55:16 +02:00
Willi Ballenthin
0714dbee0d changelog: formatting 2021-09-28 10:26:28 -06:00
Willi Ballenthin
ead8a836be Merge pull request #799 from mandiant/williballenthin-patch-1
v3.0.2
2021-09-28 10:25:10 -06:00
Willi Ballenthin
d471e66073 v3.0.2 2021-09-28 09:44:46 -06:00
Willi Ballenthin
4ddef1f60b changelog: v3.0.2 2021-09-28 09:41:12 -06:00
Moritz
7b9da896e8 Merge pull request #797 from mandiant/fix/pyinstaller-elf
PyInstaller fix: add hidden import and test
2021-09-28 17:37:36 +02:00
Moritz Raabe
41786f4ab8 add hidden import and test 2021-09-28 15:39:23 +02:00
Capa Bot
4661da729f Sync capa-testfiles submodule 2021-09-28 10:15:01 +00:00
Capa Bot
97dc40a585 Sync capa-testfiles submodule 2021-09-28 10:04:34 +00:00
Moritz
f2082f3f52 release v3.0.1 (#791)
* release v3.0.1

* Update CHANGELOG.md

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

Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2021-09-27 20:59:18 +02:00
Moritz
f87c8ced3f Merge pull request #792 from mandiant/dependabot/pip/types-psutil-5.8.8
build(deps-dev): bump types-psutil from 5.8.5 to 5.8.8
2021-09-27 16:54:49 +02:00
dependabot[bot]
f914eea8ae build(deps-dev): bump types-psutil from 5.8.5 to 5.8.8
Bumps [types-psutil](https://github.com/python/typeshed) from 5.8.5 to 5.8.8.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-27 14:18:14 +00:00
Willi Ballenthin
b41d239301 Merge pull request #790 from mandiant/refactor/viv-utils-flirt
use viv-utils functions
2021-09-23 14:29:30 -06:00
Moritz Raabe
8bb1a1cb5a use viv-utils functions 2021-09-23 19:35:14 +02:00
Willi Ballenthin
2f61bc0b05 Merge pull request #789 from mandiant/dependabot/pip/tqdm-4.62.3
build(deps): bump tqdm from 4.62.2 to 4.62.3
2021-09-23 08:26:59 -06:00
dependabot[bot]
d22557947a build(deps): bump tqdm from 4.62.2 to 4.62.3
Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.62.2 to 4.62.3.
- [Release notes](https://github.com/tqdm/tqdm/releases)
- [Commits](https://github.com/tqdm/tqdm/compare/v4.62.2...v4.62.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-23 14:24:28 +00:00
Moritz
3e44d07541 Merge pull request #786 from fireeye/williballenthin-patch-1
setup.py: bump viv dep to v1.0.5
2021-09-23 10:21:20 +02:00
Willi Ballenthin
f56b27e1c7 changelog 2021-09-22 21:39:36 -06:00
Willi Ballenthin
12075df3ba setup.py: bump viv dep to v1.0.5 2021-09-22 21:34:17 -06:00
Moritz
a8bb9620e2 Merge pull request #785 from fireeye/dependabot/pip/black-21.9b0
build(deps-dev): bump black from 21.8b0 to 21.9b0
2021-09-20 19:03:35 +02:00
dependabot[bot]
9ed4e21429 build(deps-dev): bump black from 21.8b0 to 21.9b0
Bumps [black](https://github.com/psf/black) from 21.8b0 to 21.9b0.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-20 14:02:13 +00:00
Capa Bot
5b293d675f Sync capa-testfiles submodule 2021-09-15 21:40:34 +00:00
Willi Ballenthin
5972d6576d Merge pull request #776 from fireeye/fix-775
v3.0.0
2021-09-14 21:37:37 -06:00
William Ballenthin
19ce514b5c main: handle malformed ELF files
closes #777
2021-09-14 21:35:47 -06:00
William Ballenthin
144ed80c56 readme: add reference to third blog post 2021-09-14 21:14:44 -06:00
William Ballenthin
4d34e56589 changelog: wording 2021-09-14 21:12:46 -06:00
William Ballenthin
9045770192 version: v3.0 2021-09-14 21:09:58 -06:00
William Ballenthin
4ea21d2a9c changelog: v3.0 2021-09-14 21:08:58 -06:00
Moritz
774a188d19 Merge pull request #774 from fireeye/no-flirt-elf
disable flirt matching on elf files
2021-09-14 18:59:20 +02:00
Capa Bot
bd5c125561 Sync capa rules submodule 2021-09-14 15:29:28 +00:00
Moritz
420feea0aa Update capa/main.py
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2021-09-14 17:27:40 +02:00
Capa Bot
b298f547f9 Sync capa rules submodule 2021-09-14 15:26:51 +00:00
Capa Bot
a7fe76c336 Sync capa rules submodule 2021-09-14 15:25:46 +00:00
Willi Ballenthin
9f777ba152 readme: reference ELF support 2021-09-14 09:22:33 -06:00
Moritz Raabe
cc3b56ddcb disable flirt matching on elf files 2021-09-14 13:59:38 +02:00
Moritz Raabe
0c42942a88 black code style 2021-09-14 09:57:33 +02:00
William Ballenthin
0803c6f3fa elffile: extract global features 2021-09-13 13:51:19 -06:00
William Ballenthin
02d9d37c1e *: raise NotImplementedError not NotImplemented
> NotImplementedError and NotImplemented are not interchangeable, even though they have similar names and purposes. See NotImplemented for details on when to use it.

https://docs.python.org/3/library/exceptions.html#NotImplementedError
2021-09-13 13:47:30 -06:00
William Ballenthin
c121e9219c elffile: fix mypy 2021-09-13 13:32:09 -06:00
Willi Ballenthin
297d9aaa32 Merge pull request #770 from fireeye/elffile-extractor
add light weight ElfFeatureExtractor
2021-09-13 13:27:00 -06:00
Willi Ballenthin
11644cbc31 Update capa/features/extractors/elffile.py 2021-09-13 13:20:52 -06:00
Moritz Raabe
4c6be15edc minor fixes 2021-09-13 21:15:31 +02:00
Willi Ballenthin
e1028e4dd8 Merge pull request #773 from fireeye/dependabot/pip/types-psutil-5.8.5
build(deps-dev): bump types-psutil from 5.8.2 to 5.8.5
2021-09-13 09:29:20 -06:00
dependabot[bot]
861ff1c91f build(deps-dev): bump types-psutil from 5.8.2 to 5.8.5
Bumps [types-psutil](https://github.com/python/typeshed) from 5.8.2 to 5.8.5.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-13 14:03:31 +00:00
Moritz Raabe
80bb0b4aff init variable :/ 2021-09-10 21:29:59 +02:00
Moritz Raabe
06d238a9f9 add ElfFeatureExtractor 2021-09-10 20:38:27 +02:00
mike-hunhoff
71ce28d9e6 Merge pull request #768 from fireeye/explorer/fix/745
explorer: improve parsing algorithm for rule generator feature editor
2021-09-10 10:37:52 -06:00
Moritz
c48429e5c3 Merge pull request #766 from fireeye/ci/update-ubuntu-16
update to ubuntu-18.04
2021-09-10 10:28:31 +02:00
Willi Ballenthin
34e3f7bbaf Merge pull request #759 from fireeye/fix-755
extractors: extract global features as their own pseudo scope
2021-09-09 20:16:48 -06:00
Michael Hunhoff
db624460bc explorer: improve parsing algorithm for rule generator feature editor 2021-09-09 15:45:04 -06:00
Moritz Raabe
16c12f816b update to ubuntu-18.04 2021-09-09 16:45:11 +02:00
Capa Bot
ea6fed56a2 Sync capa rules submodule 2021-09-08 14:41:58 +00:00
Moritz
22f11f1a97 Merge pull request #763 from fireeye/dependabot/pip/types-psutil-5.8.2
build(deps-dev): bump types-psutil from 5.8.0 to 5.8.2
2021-09-06 23:03:20 +02:00
Moritz
7c21ccb8f9 Merge pull request #762 from fireeye/dependabot/pip/types-pyyaml-5.4.10
build(deps-dev): bump types-pyyaml from 5.4.8 to 5.4.10
2021-09-06 23:03:11 +02:00
Moritz
8f86b0eac2 Merge pull request #761 from fireeye/dependabot/pip/pytest-6.2.5
build(deps-dev): bump pytest from 6.2.4 to 6.2.5
2021-09-06 23:03:02 +02:00
Moritz
9c8fa32e5c Merge pull request #760 from fireeye/dependabot/pip/pefile-2021.9.3
build(deps): bump pefile from 2021.5.24 to 2021.9.3
2021-09-06 23:02:54 +02:00
dependabot[bot]
9d348c6da2 build(deps-dev): bump types-psutil from 5.8.0 to 5.8.2
Bumps [types-psutil](https://github.com/python/typeshed) from 5.8.0 to 5.8.2.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-06 14:02:56 +00:00
dependabot[bot]
4dc87240f9 build(deps-dev): bump types-pyyaml from 5.4.8 to 5.4.10
Bumps [types-pyyaml](https://github.com/python/typeshed) from 5.4.8 to 5.4.10.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-06 14:02:54 +00:00
dependabot[bot]
a60d11a763 build(deps-dev): bump pytest from 6.2.4 to 6.2.5
Bumps [pytest](https://github.com/pytest-dev/pytest) from 6.2.4 to 6.2.5.
- [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/6.2.4...6.2.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-06 14:02:52 +00:00
dependabot[bot]
391cc77996 build(deps): bump pefile from 2021.5.24 to 2021.9.3
Bumps [pefile](https://github.com/erocarrera/pefile) from 2021.5.24 to 2021.9.3.
- [Release notes](https://github.com/erocarrera/pefile/releases)
- [Commits](https://github.com/erocarrera/pefile/compare/v2021.5.24...v2021.9.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-06 14:02:48 +00:00
William Ballenthin
7a3287fa25 extractors: smda: fix missing yield from 2021-09-04 16:55:37 -06:00
William Ballenthin
32244b2641 fixtures: fix extraction of global features 2021-09-04 16:12:51 -06:00
William Ballenthin
122fdc69e3 fixtures: name error 2021-09-04 16:00:49 -06:00
William Ballenthin
39e4e47763 pep8 2021-09-04 15:59:38 -06:00
William Ballenthin
2ea4dc9d7e tests: fixtures: extract global features at each scope 2021-09-04 15:58:32 -06:00
William Ballenthin
b2590e7c9a changelog 2021-09-04 15:55:28 -06:00
William Ballenthin
af6fe6baa0 extractors: extract global features as their own pseudo scope
this means they can be extracted separately in the freeze format.

closes #755
2021-09-04 15:53:05 -06:00
Moritz
ce799dadbe Merge pull request #758 from fireeye/explorer/new-feature-support
adding support for arch, os, and format features
2021-09-02 20:39:08 +02:00
Michael Hunhoff
217e6f88d9 adding support for arch, os, and format features 2021-09-02 08:29:55 -06:00
Moritz
a363baffce Merge pull request #757 from davidt99/master
fix: use netwrokx import since nx is deprecated
2021-08-31 11:02:40 +02:00
Capa Bot
bbe47d81e9 Sync capa rules submodule 2021-08-30 16:30:52 +00:00
davidt99
a105b41647 fix: use netwrokx import since nx is deprecated 2021-08-30 19:11:30 +03:00
Capa Bot
fc8919adce Sync capa-testfiles submodule 2021-08-30 15:51:01 +00:00
Willi Ballenthin
f21877ae27 Merge pull request #750 from fireeye/dependabot/pip/types-pyyaml-5.4.8
build(deps-dev): bump types-pyyaml from 5.4.6 to 5.4.8
2021-08-30 08:46:01 -06:00
Willi Ballenthin
99e7967e22 Merge pull request #752 from fireeye/dependabot/pip/ruamel-yaml-0.17.16
build(deps): bump ruamel-yaml from 0.17.13 to 0.17.16
2021-08-30 08:45:47 -06:00
Willi Ballenthin
766fe9d845 Merge pull request #754 from fireeye/dependabot/pip/black-21.8b0
build(deps-dev): bump black from 21.7b0 to 21.8b0
2021-08-30 08:44:40 -06:00
dependabot[bot]
2c60faee26 build(deps-dev): bump black from 21.7b0 to 21.8b0
Bumps [black](https://github.com/psf/black) from 21.7b0 to 21.8b0.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-30 14:02:09 +00:00
dependabot[bot]
097f1d4695 build(deps): bump ruamel-yaml from 0.17.13 to 0.17.16
Bumps [ruamel-yaml](https://sourceforge.net/p/ruamel-yaml/code/ci/default/tree) from 0.17.13 to 0.17.16.

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-30 14:02:03 +00:00
dependabot[bot]
a6efc3952f build(deps-dev): bump types-pyyaml from 5.4.6 to 5.4.8
Bumps [types-pyyaml](https://github.com/python/typeshed) from 5.4.6 to 5.4.8.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-30 14:01:55 +00:00
Moritz
dadd76bd62 Merge pull request #747 from fireeye/feature-linter-pbar
linter enhancements
2021-08-30 12:18:19 +02:00
William Ballenthin
282c0c2655 lint: guide mypy typing to address CI issues 2021-08-27 13:00:40 -06:00
William Ballenthin
14f2391f49 mypy: add devtools ignore 2021-08-27 12:33:04 -06:00
William Ballenthin
b5860190e3 linter: invoke gc 2021-08-27 09:47:34 -06:00
William Ballenthin
d8ecb88867 changelog 2021-08-27 09:37:53 -06:00
William Ballenthin
f5b2efdc87 lint: reduce logging verbosity 2021-08-27 09:36:32 -06:00
William Ballenthin
fab26180cb lint: cache analysis results per path 2021-08-27 09:24:36 -06:00
William Ballenthin
3968d40bf4 linter: use pathlib.Path 2021-08-27 09:11:28 -06:00
William Ballenthin
cb2d1cde36 linter: add typing 2021-08-27 09:04:37 -06:00
William Ballenthin
da7a9b7232 linter: don't show noisey "need example" warnings in nursery 2021-08-27 08:42:46 -06:00
William Ballenthin
4f15225665 lint: handle calls to print within pbar 2021-08-27 08:34:02 -06:00
William Ballenthin
90708c123b linter: show progress bar 2021-08-27 08:21:09 -06:00
Capa Bot
384f467d4a Sync capa rules submodule 2021-08-26 23:53:30 +00:00
Capa Bot
37064f20d1 Sync capa rules submodule 2021-08-26 23:49:07 +00:00
Willi Ballenthin
9e579f9de3 tests: viv: reenable elf tests
revert 56f9e16a8b

viv is reverted to v1.0.3 so tests should pass again ref $735
2021-08-26 16:50:57 -06:00
Willi Ballenthin
b2c688ef14 Merge pull request #746 from fireeye/revert-731-dependabot/pip/vivisect-1.0.4
Revert "build(deps): bump vivisect from 1.0.3 to 1.0.4"
2021-08-26 13:00:13 -06:00
Willi Ballenthin
9717acd988 Revert "build(deps): bump vivisect from 1.0.3 to 1.0.4" 2021-08-26 12:59:49 -06:00
mike-hunhoff
d06c5b12c2 Merge pull request #742 from fireeye/fix/740
explorer: small performance boost to rule generator search functionality
2021-08-26 10:35:20 -06:00
Capa Bot
e97a120602 Sync capa rules submodule 2021-08-26 15:12:41 +00:00
Capa Bot
5b806b08dd Sync capa rules submodule 2021-08-26 15:12:14 +00:00
Willi Ballenthin
fd5dfcc6d8 Merge pull request #743 from fireeye/feature-lint-ntoskrnl-ntdll-exceptions
fix linter ntoskrnl/ntdll exceptions
2021-08-26 08:56:45 -06:00
Michael Hunhoff
3979317b10 merging upstream 2021-08-26 08:26:41 -06:00
mike-hunhoff
8d2595a6db Update README.md 2021-08-26 08:20:38 -06:00
mike-hunhoff
3c2c452501 Merge pull request #741 from fireeye/doc/explorer-support
explorer: updating support documentation and runtime checks
2021-08-26 08:19:01 -06:00
Michael Hunhoff
af48f86e55 Merge branch 'doc/explorer-support' of github.com:fireeye/capa into doc/explorer-support 2021-08-26 08:16:25 -06:00
Michael Hunhoff
73957ea14e merging upstream 2021-08-26 08:15:25 -06:00
William Ballenthin
bb824e9167 Merge branch 'master' into feature-lint-ntoskrnl-ntdll-exceptions 2021-08-25 16:44:29 -06:00
William Ballenthin
b996e77606 setup: add psutil deps to [dev] 2021-08-25 16:43:46 -06:00
William Ballenthin
9a20bbd4e1 changelog 2021-08-25 16:39:57 -06:00
William Ballenthin
8195b7565f lint: hardcoded some exports of ntdll/ntoskrnl to reduce warning spam 2021-08-25 16:36:36 -06:00
William Ballenthin
0569f9b242 lint: show mod/imp names per rule
fix bug where the same mod/imp name pair was shown for all rules
2021-08-25 16:36:08 -06:00
Michael Hunhoff
8ffa8ea2c8 explorer: small performance boost to rule generator search functionality 2021-08-25 15:45:47 -06:00
Capa Bot
fd7cff6109 Sync capa rules submodule 2021-08-25 20:34:00 +00:00
mike-hunhoff
a3b292066a Update capa/ida/helpers.py
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2021-08-25 13:03:45 -06:00
Michael Hunhoff
8f6d38468e explorer: updating support documentation and runtime checks 2021-08-25 12:46:34 -06:00
William Ballenthin
4af5cc66ba changelog 2021-08-24 17:53:56 -06:00
William Ballenthin
33c3c7e106 scripts: profile-memory: show vms, too 2021-08-24 17:26:45 -06:00
William Ballenthin
5c75f12b78 scripts: profile-memory: show incremental duration and RSS 2021-08-24 17:22:18 -06:00
William Ballenthin
1ae6638861 Merge branch 'master' of github.com:fireeye/capa 2021-08-24 17:05:59 -06:00
William Ballenthin
d8999471c5 scripts: add profile-memory
ref #736
2021-08-24 17:05:34 -06:00
Capa Bot
90c0de1a7f Sync capa rules submodule 2021-08-24 22:48:07 +00:00
Capa Bot
d13ea1cbbe Sync capa rules submodule 2021-08-24 22:34:04 +00:00
Willi Ballenthin
03cf28fccd Merge pull request #739 from fireeye/feature-737
rules: add substring feature
2021-08-24 16:33:17 -06:00
William Ballenthin
8e757d2099 show-features: print function addresses, too 2021-08-24 16:32:44 -06:00
William Ballenthin
2989732637 tests: fix fva of substring test function 2021-08-24 16:32:27 -06:00
William Ballenthin
db45068357 tests: fix tests for substring 2021-08-24 16:13:41 -06:00
Capa Bot
735aea86e0 Sync capa rules submodule 2021-08-24 18:41:34 +00:00
William Ballenthin
d8c8c6d2f3 lint: apply string lints to substrings, too 2021-08-24 11:52:28 -06:00
William Ballenthin
3b4cb47597 pep8 2021-08-24 11:45:48 -06:00
William Ballenthin
f55e758d47 tests: rules: demonstrate substring with description 2021-08-24 11:45:24 -06:00
William Ballenthin
c5a5e5600a changelog: substring 2021-08-24 11:37:07 -06:00
William Ballenthin
6989e8b8cf rules: add substring feature
closes #737
2021-08-24 11:35:01 -06:00
Capa Bot
7d2e550b84 Sync capa rules submodule 2021-08-24 16:35:30 +00:00
Capa Bot
7f17c45b69 Sync capa rules submodule 2021-08-24 16:06:15 +00:00
Willi Ballenthin
b0c86ab8db Merge pull request #738 from fireeye/revert-697-dependabot/pip/networkx-2.6.2
Revert "build(deps): bump networkx from 2.5.1 to 2.6.2"
2021-08-24 09:50:49 -06:00
Willi Ballenthin
4c0c2c75c6 Revert "build(deps): bump networkx from 2.5.1 to 2.6.2" 2021-08-24 09:50:39 -06:00
Capa Bot
1549b9b506 Sync capa rules submodule 2021-08-24 15:47:44 +00:00
Capa Bot
057eeb3629 Sync capa-testfiles submodule 2021-08-24 15:45:39 +00:00
Capa Bot
0dea4e8b7d Sync capa-testfiles submodule 2021-08-24 15:45:04 +00:00
Willi Ballenthin
d3573a565c Merge pull request #723 from fireeye/feature-701
os, arch, and format features
2021-08-24 08:56:29 -06:00
Willi Ballenthin
1275b49ebb Merge pull request #697 from fireeye/dependabot/pip/networkx-2.6.2
build(deps): bump networkx from 2.5.1 to 2.6.2
2021-08-24 08:56:17 -06:00
William Ballenthin
56f9e16a8b tests: viv: disable ELF tests due to #735 2021-08-23 17:51:28 -06:00
William Ballenthin
a4b0954532 viv: ignore mypy FP 2021-08-23 16:57:35 -06:00
William Ballenthin
fc73787849 extractors: file extractor arg consistency via kwargs 2021-08-23 16:42:16 -06:00
William Ballenthin
30a5493414 tests: smda: remove unused import 2021-08-23 16:13:01 -06:00
William Ballenthin
a729bdfbe6 elf: more clearly set first detected OS 2021-08-23 16:12:07 -06:00
William Ballenthin
dab88e482d elf: add more explanation about ei_osabi 2021-08-23 16:08:01 -06:00
William Ballenthin
6482f67a0c elf: document unused OS constants 2021-08-23 16:06:14 -06:00
William Ballenthin
a1bf95ec2c features: formatting of OS constants 2021-08-23 16:00:57 -06:00
William Ballenthin
6961fde327 Merge branch 'feature-701' of github.com:fireeye/capa into feature-701 2021-08-23 15:59:09 -06:00
William Ballenthin
c0fe0420fc changelog: tweak PR ref 2021-08-23 15:58:32 -06:00
Willi Ballenthin
2ba000a987 Merge branch 'master' into feature-701 2021-08-23 10:02:41 -06:00
Willi Ballenthin
a90e93e150 Update capa/main.py
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2021-08-23 08:54:43 -06:00
Willi Ballenthin
b6ab12d3c1 Update capa/features/common.py
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2021-08-23 08:54:22 -06:00
dependabot[bot]
71ccd87435 build(deps): bump networkx from 2.5.1 to 2.6.2
Bumps [networkx](https://github.com/networkx/networkx) from 2.5.1 to 2.6.2.
- [Release notes](https://github.com/networkx/networkx/releases)
- [Commits](https://github.com/networkx/networkx/compare/networkx-2.5.1...networkx-2.6.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-23 14:24:19 +00:00
Willi Ballenthin
d07045f134 Merge pull request #731 from fireeye/dependabot/pip/vivisect-1.0.4
build(deps): bump vivisect from 1.0.3 to 1.0.4
2021-08-23 08:23:36 -06:00
dependabot[bot]
bede4a0aa1 build(deps): bump vivisect from 1.0.3 to 1.0.4
Bumps [vivisect](https://github.com/vivisect/vivisect) from 1.0.3 to 1.0.4.
- [Release notes](https://github.com/vivisect/vivisect/releases)
- [Changelog](https://github.com/vivisect/vivisect/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/vivisect/vivisect/compare/v1.0.3...v1.0.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-23 14:22:20 +00:00
Willi Ballenthin
de1cff356a Merge pull request #733 from fireeye/dependabot/pip/tqdm-4.62.2
build(deps): bump tqdm from 4.62.1 to 4.62.2
2021-08-23 08:21:56 -06:00
Willi Ballenthin
1bee098fb6 Merge pull request #734 from fireeye/dependabot/pip/smda-1.6.2
build(deps): bump smda from 1.5.19 to 1.6.2
2021-08-23 08:21:00 -06:00
dependabot[bot]
e36e175e08 build(deps): bump smda from 1.5.19 to 1.6.2
Bumps [smda](https://github.com/danielplohmann/smda) from 1.5.19 to 1.6.2.
- [Release notes](https://github.com/danielplohmann/smda/releases)
- [Commits](https://github.com/danielplohmann/smda/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-23 14:20:55 +00:00
Willi Ballenthin
9db45d2fcb Merge pull request #732 from fireeye/dependabot/pip/ruamel-yaml-0.17.13
build(deps): bump ruamel-yaml from 0.17.10 to 0.17.13
2021-08-23 08:20:07 -06:00
dependabot[bot]
558f5d0c8a build(deps): bump tqdm from 4.62.1 to 4.62.2
Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.62.1 to 4.62.2.
- [Release notes](https://github.com/tqdm/tqdm/releases)
- [Commits](https://github.com/tqdm/tqdm/compare/v4.62.1...v4.62.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-23 14:02:36 +00:00
dependabot[bot]
e32a887091 build(deps): bump ruamel-yaml from 0.17.10 to 0.17.13
Bumps [ruamel-yaml](https://sourceforge.net/p/ruamel-yaml/code/ci/default/tree) from 0.17.10 to 0.17.13.

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-23 14:02:32 +00:00
William Ballenthin
1b9a6c3c59 main: collect os/format/arch into metadata and render it 2021-08-20 16:50:40 -06:00
William Ballenthin
aef03b5592 elf: fix type error caught by mypy! 2021-08-20 15:00:06 -06:00
William Ballenthin
3eaeb533e9 Merge branch 'feature-701' of github.com:fireeye/capa into feature-701 2021-08-20 14:56:53 -06:00
William Ballenthin
04cc94a450 main: detect invalid arch and os 2021-08-20 14:56:26 -06:00
Willi Ballenthin
dae7be076d elf: fix alignment calculation
identified over [here](14f9c972b3 (r692441396))
2021-08-19 14:45:08 -06:00
Michael Hunhoff
3cb7573edb enable os/arch/format for capa explorer 2021-08-19 13:06:43 -06:00
William Ballenthin
a96a5de12d tests: re-enable SMDA ELF API tests 2021-08-19 08:02:17 -06:00
William Ballenthin
45b6c8dad3 setup: bump SMDA dep ver
closes #725
2021-08-19 08:01:17 -06:00
William Ballenthin
cf17ebac33 Merge branch 'feature-701' of github.com:fireeye/capa into feature-701 2021-08-18 16:33:21 -06:00
William Ballenthin
f0a34fdb5e merge 2021-08-18 16:32:58 -06:00
Willi Ballenthin
e124115e8d Merge branch 'master' into feature-701 2021-08-18 16:29:05 -06:00
William Ballenthin
249b8498d9 pefile: extract Arch 2021-08-18 16:27:41 -06:00
Capa Bot
15c69e3b7d Sync capa rules submodule 2021-08-18 21:15:01 +00:00
Capa Bot
98208b8eec Sync capa rules submodule 2021-08-18 20:50:11 +00:00
Capa Bot
0690e73320 Sync capa rules submodule 2021-08-18 20:38:06 +00:00
William Ballenthin
766ac7e500 Merge branch 'master' of github.com:fireeye/capa into feature-701 2021-08-18 14:33:17 -06:00
Capa Bot
51ac57c657 Sync capa-testfiles submodule 2021-08-18 20:33:02 +00:00
William Ballenthin
89603586da elf: add some doc 2021-08-18 14:23:48 -06:00
William Ballenthin
a35f5a1650 elf: detect FreeBSD via note 2021-08-18 14:21:50 -06:00
William Ballenthin
f1df29d27e tests: xfail smda ELF API
waiting for #725
2021-08-18 14:08:36 -06:00
Willi Ballenthin
08c24e2705 Merge pull request #729 from doomedraven/patch-1
update capa_as_library for capa v2
2021-08-18 08:32:41 -06:00
doomedraven
b1171864e3 black 2021-08-18 14:25:58 +02:00
doomedraven
5af59cecda update capa_as_library for capa v2 2021-08-18 14:23:36 +02:00
William Ballenthin
0c3a38b24b Merge branch 'feature-701' of github.com:fireeye/capa into feature-701 2021-08-17 09:07:25 -06:00
William Ballenthin
ac5d163aa0 pep8 2021-08-17 09:07:08 -06:00
Willi Ballenthin
dfe2dbea6d Merge pull request #722 from fireeye/fix-703
fix reporting of namespace matches
2021-08-17 09:05:19 -06:00
Willi Ballenthin
909ffc187b Merge branch 'master' into feature-701 2021-08-17 09:00:48 -06:00
William Ballenthin
92dfa99059 extractors: log unsupported os/arch/format but don't except 2021-08-17 08:57:42 -06:00
William Ballenthin
0065876702 extractors: ida: move os extraction to global module 2021-08-17 08:57:27 -06:00
Capa Bot
23bf28702f Sync capa rules submodule 2021-08-17 14:23:23 +00:00
Capa Bot
066873bd06 Sync capa rules submodule 2021-08-17 14:20:34 +00:00
William Ballenthin
98c00bd8b1 extractors: add missing global_.py files 2021-08-16 17:12:45 -06:00
William Ballenthin
fd47b03fac render: vverbose: don't render locations of global scope features 2021-08-16 17:12:28 -06:00
William Ballenthin
8e689c39f4 features: add Arch feature at global scope 2021-08-16 17:06:56 -06:00
William Ballenthin
738fa9150e fixtures: update tests to account for Format scope 2021-08-16 16:39:40 -06:00
William Ballenthin
5405e182c3 features: move Format features to file scope 2021-08-16 16:37:04 -06:00
William Ballenthin
ab1326f858 features: move OS and Format to their own features, not characteristics 2021-08-16 16:28:26 -06:00
William Ballenthin
f013815b2a features: rename legacy term arch to bitness
makes space for upcoming feature `arch: ` for things like i386/amd64/aarch64
2021-08-16 12:21:25 -06:00
Willi Ballenthin
5b24fc2543 Merge pull request #727 from fireeye/dependabot/pip/tqdm-4.62.1
build(deps): bump tqdm from 4.62.0 to 4.62.1
2021-08-16 08:22:44 -06:00
dependabot[bot]
b103e40ba8 build(deps): bump tqdm from 4.62.0 to 4.62.1
Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.62.0 to 4.62.1.
- [Release notes](https://github.com/tqdm/tqdm/releases)
- [Commits](https://github.com/tqdm/tqdm/compare/v4.62.0...v4.62.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-16 14:02:16 +00:00
William Ballenthin
d5c9a5cf3c mypy: ignore ida_loader 2021-08-11 15:15:33 -06:00
William Ballenthin
30d7425b98 changelog 2021-08-11 15:10:07 -06:00
William Ballenthin
34819b289d pep8 2021-08-11 15:08:31 -06:00
William Ballenthin
71d9ebd859 extractors: ida: extract OS and file format characteristics at all scopes 2021-08-11 15:05:57 -06:00
William Ballenthin
c1910d47f0 move is_global_feature into capa.features.common 2021-08-11 15:02:10 -06:00
William Ballenthin
769d354792 detect-elf-os: remove extra print statement 2021-08-11 14:56:01 -06:00
William Ballenthin
a7678e779e extractors: smda: extract format and OS characteristics at all scopes 2021-08-11 14:52:36 -06:00
William Ballenthin
294f74b209 extractors: viv: extract format and OS at all scopes 2021-08-11 14:44:41 -06:00
William Ballenthin
fa8b4a4203 extractors: add common routine to extract OS from ELF 2021-08-11 14:43:13 -06:00
William Ballenthin
7205862dbf helpers: move ELF and IDA helpers out of script and into common module 2021-08-11 14:42:29 -06:00
William Ballenthin
37bc47c772 extractors: viv: extract from bytes not file path 2021-08-11 14:41:11 -06:00
William Ballenthin
baaa8ba2c1 scripts: add script to detect ELF OS
closes #724
2021-08-11 13:52:50 -06:00
William Ballenthin
05f8e2445a fixtures: add tests demonstrating extraction of features from ELF files 2021-08-11 09:29:05 -06:00
William Ballenthin
753b003107 pep8 2021-08-11 09:23:41 -06:00
William Ballenthin
97092c91db tests: assert absence of the wrong os/format 2021-08-11 09:13:56 -06:00
William Ballenthin
20859d2796 extractors: pefile: extract OS and format 2021-08-11 09:11:29 -06:00
William Ballenthin
06f8943bc4 features: add format/pe and format/elf characteristics 2021-08-11 09:10:04 -06:00
William Ballenthin
e797a67e97 features: define CHARACTERISTIC_OS constants for ease of use 2021-08-11 09:08:37 -06:00
William Ballenthin
a1eca58d7a features: support characteristic(os/*) features 2021-08-11 08:40:40 -06:00
William Ballenthin
aefe97e09e rules: fix typos 2021-08-11 08:39:56 -06:00
Willi Ballenthin
59ae901f57 changelog 2021-08-11 08:21:38 -06:00
Capa Bot
811f484d3b Sync capa-testfiles submodule 2021-08-11 14:18:28 +00:00
Willi Ballenthin
ff08b99190 Merge pull request #700 from Adir-Shemesh/elf
Add initial elf files support
2021-08-11 08:18:02 -06:00
William Ballenthin
44dc4efe57 changlog 2021-08-10 13:14:00 -06:00
William Ballenthin
f7e2ac83f2 Merge branch 'master' of github.com:fireeye/capa into fix-703 2021-08-10 13:12:25 -06:00
William Ballenthin
7e60162d65 result_document: extract only the relevant namespace locations
closes #703
2021-08-10 13:06:04 -06:00
William Ballenthin
cd06ee4544 main: correctly extract namespaces matches across scopes
closes #721
2021-08-10 13:05:31 -06:00
Willi Ballenthin
6d0a777de6 pefile: handle case where no name is exported
closes #684
2021-08-09 20:28:25 -06:00
Capa Bot
dd7a48a00c Sync capa rules submodule 2021-08-09 19:52:39 +00:00
Willi Ballenthin
582dcef097 Merge pull request #718 from fireeye/dependabot/pip/types-tabulate-0.8.2
build(deps-dev): bump types-tabulate from 0.8.0 to 0.8.2
2021-08-09 09:55:27 -06:00
dependabot[bot]
b9501d7b77 build(deps-dev): bump types-tabulate from 0.8.0 to 0.8.2
Bumps [types-tabulate](https://github.com/python/typeshed) from 0.8.0 to 0.8.2.
- [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>
2021-08-09 15:49:55 +00:00
Willi Ballenthin
a523fcf804 Merge pull request #717 from fireeye/dependabot/pip/types-termcolor-1.1.1
build(deps-dev): bump types-termcolor from 0.1.1 to 1.1.1
2021-08-09 09:49:16 -06:00
dependabot[bot]
cd07745af1 build(deps-dev): bump types-termcolor from 0.1.1 to 1.1.1
Bumps [types-termcolor](https://github.com/python/typeshed) from 0.1.1 to 1.1.1.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-09 15:41:25 +00:00
Willi Ballenthin
6c15881bfe Merge pull request #716 from fireeye/dependabot/pip/types-pyyaml-5.4.6
build(deps-dev): bump types-pyyaml from 5.4.3 to 5.4.6
2021-08-09 09:40:40 -06:00
dependabot[bot]
7ff358ee00 build(deps-dev): bump types-pyyaml from 5.4.3 to 5.4.6
Bumps [types-pyyaml](https://github.com/python/typeshed) from 5.4.3 to 5.4.6.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-09 15:39:34 +00:00
Willi Ballenthin
79e5fad326 Merge pull request #715 from fireeye/dependabot/pip/types-colorama-0.4.3
build(deps-dev): bump types-colorama from 0.4.2 to 0.4.3
2021-08-09 09:38:48 -06:00
dependabot[bot]
93f5e966b2 build(deps-dev): bump types-colorama from 0.4.2 to 0.4.3
Bumps [types-colorama](https://github.com/python/typeshed) from 0.4.2 to 0.4.3.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-09 14:02:06 +00:00
adirshemesh
d0e9c004a0 Add initial elf files support 2021-08-05 15:24:22 +03:00
Capa Bot
4814a47560 Sync capa rules submodule 2021-08-03 14:10:25 +00:00
Willi Ballenthin
3c81d91072 Merge pull request #696 from fireeye/dependabot/pip/tqdm-4.62.0
build(deps): bump tqdm from 4.61.2 to 4.62.0
2021-08-02 08:43:26 -06:00
Willi Ballenthin
de21f9a1f9 Merge pull request #695 from fireeye/dependabot/pip/types-tabulate-0.8.0
build(deps-dev): bump types-tabulate from 0.1.1 to 0.8.0
2021-08-02 08:43:12 -06:00
Willi Ballenthin
9f4dab89a5 Merge pull request #694 from fireeye/dependabot/pip/isort-5.9.3
build(deps-dev): bump isort from 5.9.2 to 5.9.3
2021-08-02 08:43:01 -06:00
dependabot[bot]
9def3df16f build(deps): bump tqdm from 4.61.2 to 4.62.0
Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.61.2 to 4.62.0.
- [Release notes](https://github.com/tqdm/tqdm/releases)
- [Commits](https://github.com/tqdm/tqdm/compare/v4.61.2...v4.62.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-02 14:02:28 +00:00
dependabot[bot]
44dd56e344 build(deps-dev): bump types-tabulate from 0.1.1 to 0.8.0
Bumps [types-tabulate](https://github.com/python/typeshed) from 0.1.1 to 0.8.0.
- [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-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-02 14:02:24 +00:00
dependabot[bot]
e630bd06db build(deps-dev): bump isort from 5.9.2 to 5.9.3
Bumps [isort](https://github.com/pycqa/isort) from 5.9.2 to 5.9.3.
- [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.9.2...5.9.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-02 14:02:22 +00:00
Capa Bot
1fbd4937bc Sync capa rules submodule 2021-07-29 19:33:40 +00:00
Capa Bot
cc54bdddc6 Sync capa rules submodule 2021-07-29 18:44:43 +00:00
Capa Bot
f750455519 Sync capa rules submodule 2021-07-29 18:42:08 +00:00
mike-hunhoff
3d383bcc57 Merge pull request #692 from fireeye/explorer/enhance-limit-features-to-selection
add option to limit features to currently selected disassembly address
2021-07-29 09:20:36 -06:00
Michael Hunhoff
cdab6eaa5d updating CHANGELOG 2021-07-28 13:50:50 -06:00
Michael Hunhoff
7937cb6ea3 updating API calls 2021-07-28 13:44:06 -06:00
Michael Hunhoff
57f5236c9b adding option to filter features by currenty disassembly address 2021-07-28 13:38:36 -06:00
mike-hunhoff
f7bdd0e7f6 Merge pull request #691 from fireeye/fix/690
enforce max column width Features and Editor panes
2021-07-28 12:10:02 -06:00
Michael Hunhoff
a108e385fe updating changelog 2021-07-28 09:07:22 -06:00
Michael Hunhoff
6549c9878b merge upstream 2021-07-28 09:06:30 -06:00
Michael Hunhoff
a3a760e1e6 limit column sizes for Features and Editor panes 2021-07-28 08:53:12 -06:00
mike-hunhoff
576b9be78c Merge pull request #689 from fireeye/fix/544
add option to select specificed byte count for bytes feature
2021-07-27 16:12:26 -06:00
Michael Hunhoff
528548eb8c add option to select specificed byte count for bytes feature 2021-07-27 15:18:13 -06:00
mike-hunhoff
9a2415e34e Merge pull request #688 from fireeye/fix/514
update IDA extractor to use non-canon mnemonics
2021-07-27 14:56:14 -06:00
Michael Hunhoff
c9b7162a5f update IDA extractor to use non-canon mnemonics 2021-07-27 13:34:52 -06:00
mike-hunhoff
7fd9ab5e88 Merge pull request #687 from fireeye/fix/655
remove duplicate check when saving file
2021-07-27 10:49:23 -06:00
Michael Hunhoff
b44edbd90e remove duplicate check when saving file 2021-07-27 09:50:25 -06:00
mike-hunhoff
a1b3703a0d Merge pull request #686 from fireeye/fix/531
add additional filter logic when displaying capa matches by function
2021-07-27 08:48:35 -06:00
Michael Hunhoff
874dffc13f add additional filter logic when displaying capa matches by function 2021-07-26 17:37:35 -06:00
Capa Bot
8b572dc63f Sync capa rules submodule 2021-07-26 21:48:37 +00:00
Willi Ballenthin
659b29a62d Merge pull request #685 from fireeye/dependabot/pip/smda-1.5.19
build(deps): bump smda from 1.5.18 to 1.5.19
2021-07-26 09:22:22 -06:00
dependabot[bot]
7a558898e1 build(deps): bump smda from 1.5.18 to 1.5.19
Bumps [smda](https://github.com/danielplohmann/smda) from 1.5.18 to 1.5.19.
- [Release notes](https://github.com/danielplohmann/smda/releases)
- [Commits](https://github.com/danielplohmann/smda/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-26 14:02:46 +00:00
Capa Bot
7dee553558 Sync capa rules submodule 2021-07-23 16:39:19 +00:00
Capa Bot
9f6f18466a Sync capa rules submodule 2021-07-22 06:56:23 +00:00
Capa Bot
ef003366da Sync capa-testfiles submodule 2021-07-21 07:12:59 +00:00
Moritz
aaaadc2a47 Update installation.md (#679)
* Update installation.md

* Update doc/installation.md

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

Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2021-07-20 20:01:10 +02:00
Willi Ballenthin
f94287c9ae Merge pull request #678 from fireeye/mr-tz-patch-1
Update README.md
2021-07-19 14:31:37 -06:00
Moritz
c56bfdca67 Update README.md 2021-07-19 21:10:20 +02:00
Willi Ballenthin
77a86e33bd Merge pull request #671 from Ana06/release2
Release capa v2.0 🎉
2021-07-19 10:32:34 -06:00
Willi Ballenthin
4f44b5a60a Merge pull request #677 from fireeye/dependabot/pip/black-21.7b0
build(deps-dev): bump black from 21.6b0 to 21.7b0
2021-07-19 10:01:45 -06:00
dependabot[bot]
9361b3deb1 build(deps-dev): bump black from 21.6b0 to 21.7b0
Bumps [black](https://github.com/psf/black) from 21.6b0 to 21.7b0.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-19 14:02:42 +00:00
Ana Maria Martinez Gomez
9a0ec51f00 changelog: update date and number of rules 2021-07-16 17:37:03 +02:00
Ana Maria Martinez Gomez
5979892d29 version: capa v2.0
Prepare capa/version for capa 2.0 release.
2021-07-16 17:34:14 +02:00
Ana Maria Martinez Gomez
96f2536c34 changelog: capa v2.0
Prepare changelog for capa v2.0 release.
2021-07-16 17:34:13 +02:00
Capa Bot
52a3d35987 Sync capa rules submodule 2021-07-13 18:39:44 +00:00
Capa Bot
de4827e8fa Sync capa rules submodule 2021-07-13 18:34:24 +00:00
Capa Bot
b6d5409691 Sync capa rules submodule 2021-07-13 18:33:06 +00:00
Capa Bot
818f532ca9 Sync capa rules submodule 2021-07-13 18:31:57 +00:00
Capa Bot
895b548f34 Sync capa rules submodule 2021-07-13 03:09:36 +00:00
Willi Ballenthin
d9f1d0918f Merge pull request #675 from fireeye/dependabot/pip/isort-5.9.2
build(deps-dev): bump isort from 5.9.1 to 5.9.2
2021-07-12 10:33:16 -06:00
Willi Ballenthin
35abdb8ecf Merge pull request #674 from fireeye/dependabot/pip/tqdm-4.61.2
build(deps): bump tqdm from 4.61.1 to 4.61.2
2021-07-12 10:32:38 -06:00
dependabot[bot]
e77bbd68cf build(deps-dev): bump isort from 5.9.1 to 5.9.2
Bumps [isort](https://github.com/pycqa/isort) from 5.9.1 to 5.9.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.9.1...5.9.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-12 14:02:19 +00:00
dependabot[bot]
4c73e5df3c build(deps): bump tqdm from 4.61.1 to 4.61.2
Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.61.1 to 4.61.2.
- [Release notes](https://github.com/tqdm/tqdm/releases)
- [Commits](https://github.com/tqdm/tqdm/compare/v4.61.1...v4.61.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-12 14:02:13 +00:00
Moritz
933789d02b Merge pull request #670 from fireeye/better-sig-loading
Better sig loading
2021-06-30 18:56:39 +02:00
Moritz Raabe
e88bb4814e update readme 2021-06-30 10:10:44 +02:00
Moritz
17b7694170 Merge pull request #666 from fireeye/fix-656
main: load signatures in order of their basename
2021-06-30 10:04:35 +02:00
Moritz Raabe
f191c4f145 wrap sig loading 2021-06-30 10:04:11 +02:00
Moritz Raabe
6fc2037f45 update sig file names 2021-06-30 08:54:37 +02:00
Moritz
b5f23e7baf Merge pull request #660 from fireeye/ci/test-scripts
test scripts and fix show-features
2021-06-29 21:46:43 +02:00
Capa Bot
f7e4273523 Sync capa rules submodule 2021-06-29 19:22:47 +00:00
Moritz Raabe
6860b9a040 address Willi's feedback 2021-06-29 21:16:31 +02:00
Moritz Raabe
5c8a4aafd7 test scripts and fix show-features 2021-06-29 21:16:31 +02:00
Moritz Raabe
02658d6962 do not process non-pe even with --format pe 2021-06-29 21:16:31 +02:00
William Ballenthin
b2b94e6a8e main: load signatures in order of their basename
closes #656
2021-06-29 10:52:07 -06:00
Moritz
65b3c046a3 Merge pull request #661 from fireeye/ida/extract-api-flirt
ida extract library funcs identified via flirt
2021-06-29 09:23:21 +02:00
Moritz Raabe
04b5949a05 address Mike's feedback 2021-06-29 08:57:43 +02:00
Moritz Raabe
18c87e4e55 ida extract library funcs identified via flirt 2021-06-29 08:49:48 +02:00
Willi Ballenthin
b84cc3128d Merge pull request #664 from fireeye/verify-pe-format
do not process non-pe even with --format pe
2021-06-28 12:09:54 -06:00
Willi Ballenthin
f83ef470cb Merge pull request #662 from fireeye/dependabot/pip/mypy-0.910
build(deps-dev): bump mypy from 0.902 to 0.910
2021-06-28 11:54:28 -06:00
Willi Ballenthin
2928dd279c Merge pull request #663 from fireeye/dependabot/pip/ruamel-yaml-0.17.10
build(deps): bump ruamel-yaml from 0.17.9 to 0.17.10
2021-06-28 11:54:15 -06:00
Moritz Raabe
f96d3fd8ba do not process non-pe even with --format pe 2021-06-28 18:21:01 +02:00
dependabot[bot]
d094272e4a build(deps): bump ruamel-yaml from 0.17.9 to 0.17.10
Bumps [ruamel-yaml](https://sourceforge.net/p/ruamel-yaml/code/ci/default/tree) from 0.17.9 to 0.17.10.

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-28 14:02:54 +00:00
dependabot[bot]
7eeab35ae8 build(deps-dev): bump mypy from 0.902 to 0.910
Bumps [mypy](https://github.com/python/mypy) from 0.902 to 0.910.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v0.902...v0.910)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-28 14:02:45 +00:00
Moritz
4e7b490bc3 Merge pull request #639 from fireeye/fix-630
more intuitive signature loading
2021-06-28 12:53:58 +02:00
Moritz Raabe
4ca9e168fe Merge branch 'master' into fix-630 2021-06-28 11:32:27 +02:00
Ana María Martínez Gómez
e579edecb4 Merge pull request #548 from Ana06/explorer-analyze
explorer: add analyze option
2021-06-24 12:22:24 +02:00
Capa Bot
58aa3e33bf Sync capa rules submodule 2021-06-24 00:33:45 +00:00
Ana Maria Martinez Gomez
0685d36220 explorer: use bitmask + enum for analyze option 2021-06-23 11:23:27 +02:00
Ana Maria Martinez Gomez
2158be0a2e explorer: add analyze option
I would like to load capa explorer with an script and that it runs the
analysis without needing extra clicks. Introduce an analyze option for
this.

Loading capa explorer from the UI or with Alt+F5 behaves as before. The
following command as well:
```
ida_loader.load_and_run_plugin("capa_explorer", 0)
```
But the following command automatically runs the analysis without extra
clicks:
```
ida_loader.load_and_run_plugin("capa_explorer", 1)
```

Example of where I am using this:
https://github.com/Ana06/idapython/blob/master/idapythonrc.py#L22
2021-06-23 11:23:27 +02:00
Moritz
7922d08fd4 Merge pull request #617 from fireeye/changelog-reorg
changelog: add breaking change section and reorg
2021-06-23 07:47:53 +02:00
Moritz Raabe
44b47eb39c update release checklist 2021-06-23 07:44:08 +02:00
Moritz Raabe
45c4b4019a move breaking changes to top 2021-06-23 07:44:05 +02:00
Moritz Raabe
831dc577f4 add breaking change section and reorg 2021-06-23 07:40:33 +02:00
Willi Ballenthin
229d5ca549 Merge pull request #654 from fireeye/fix/653
resolve circular import failure
2021-06-22 17:47:06 -06:00
Michael Hunhoff
2872db8b23 resolve circular import failure 2021-06-22 16:12:07 -06:00
Moritz
7152525dbc Merge pull request #648 from fireeye/mr-tz-patch-1
update dependabot actor name
2021-06-22 09:07:12 +02:00
Willi Ballenthin
d7d7aa76c8 Merge pull request #651 from fireeye/dependabot/pip/mypy-0.902
build(deps-dev): bump mypy from 0.901 to 0.902
2021-06-21 10:49:53 -06:00
dependabot[bot]
565bb96c9e build(deps-dev): bump mypy from 0.901 to 0.902
Bumps [mypy](https://github.com/python/mypy) from 0.901 to 0.902.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v0.901...v0.902)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-21 16:47:28 +00:00
Willi Ballenthin
9fd6098e1e Merge pull request #650 from fireeye/dependabot/pip/types-backports-0.1.3
build(deps-dev): bump types-backports from 0.1.2 to 0.1.3
2021-06-21 10:47:10 -06:00
Willi Ballenthin
0c0929fd94 Merge pull request #649 from fireeye/dependabot/pip/isort-5.9.1
build(deps-dev): bump isort from 5.8.0 to 5.9.1
2021-06-21 10:46:59 -06:00
Willi Ballenthin
1343baa250 Merge pull request #646 from fireeye/dependabot/pip/types-pyyaml-5.4.3
build(deps-dev): bump types-pyyaml from 0.1.6 to 5.4.3
2021-06-21 10:46:43 -06:00
dependabot[bot]
6977477a39 build(deps-dev): bump types-backports from 0.1.2 to 0.1.3
Bumps [types-backports](https://github.com/python/typeshed) from 0.1.2 to 0.1.3.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-21 16:44:34 +00:00
dependabot[bot]
86b3438a2d build(deps-dev): bump isort from 5.8.0 to 5.9.1
Bumps [isort](https://github.com/pycqa/isort) from 5.8.0 to 5.9.1.
- [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.8.0...5.9.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-21 16:44:34 +00:00
dependabot[bot]
a00c3b6d32 build(deps-dev): bump types-pyyaml from 0.1.6 to 5.4.3
Bumps [types-pyyaml](https://github.com/python/typeshed) from 0.1.6 to 5.4.3.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-21 16:44:34 +00:00
Willi Ballenthin
544ffdea8f Merge pull request #647 from fireeye/dependabot/pip/types-tabulate-0.1.1
build(deps-dev): bump types-tabulate from 0.1.0 to 0.1.1
2021-06-21 10:43:55 -06:00
dependabot[bot]
e4b89f1d7b build(deps-dev): bump types-tabulate from 0.1.0 to 0.1.1
Bumps [types-tabulate](https://github.com/python/typeshed) from 0.1.0 to 0.1.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>
2021-06-21 16:43:23 +00:00
Willi Ballenthin
73dd49ed21 Merge pull request #645 from fireeye/dependabot/pip/viv-utils-flirt--0.6.5
build(deps): bump viv-utils[flirt] from 0.6.4 to 0.6.5
2021-06-21 10:43:11 -06:00
Willi Ballenthin
0511eec67c Merge pull request #644 from fireeye/dependabot/pip/types-termcolor-0.1.1
build(deps-dev): bump types-termcolor from 0.1.0 to 0.1.1
2021-06-21 10:42:56 -06:00
Willi Ballenthin
c7e2ca0b1a Merge pull request #643 from fireeye/dependabot/pip/types-colorama-0.4.2
build(deps-dev): bump types-colorama from 0.4.0 to 0.4.2
2021-06-21 10:42:46 -06:00
Capa Bot
03b15ce289 Sync capa rules submodule 2021-06-21 14:30:00 +00:00
Moritz
2d7ac73caa update dependabot actor name 2021-06-21 16:24:43 +02:00
dependabot[bot]
7fe53073fe build(deps): bump viv-utils[flirt] from 0.6.4 to 0.6.5
Bumps [viv-utils[flirt]](https://github.com/williballenthin/viv-utils) from 0.6.4 to 0.6.5.
- [Release notes](https://github.com/williballenthin/viv-utils/releases)
- [Commits](https://github.com/williballenthin/viv-utils/compare/v0.6.4...v0.6.5)

---
updated-dependencies:
- dependency-name: viv-utils[flirt]
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-21 14:02:11 +00:00
dependabot[bot]
d1407f0a1e build(deps-dev): bump types-termcolor from 0.1.0 to 0.1.1
Bumps [types-termcolor](https://github.com/python/typeshed) from 0.1.0 to 0.1.1.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-21 14:02:08 +00:00
dependabot[bot]
f5a0e1cd08 build(deps-dev): bump types-colorama from 0.4.0 to 0.4.2
Bumps [types-colorama](https://github.com/python/typeshed) from 0.4.0 to 0.4.2.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-21 14:02:06 +00:00
Willi Ballenthin
94485285f3 Merge pull request #640 from fireeye/fix-507
disable viv creation by default
2021-06-15 15:06:40 -06:00
Willi Ballenthin
466bc4995b Update CHANGELOG.md
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2021-06-15 15:06:34 -06:00
William Ballenthin
7bce202122 doc: explain CAPA_SAVE_WORKSPACE 2021-06-15 12:31:56 -06:00
William Ballenthin
40c7401f0a pep8 2021-06-15 12:28:45 -06:00
William Ballenthin
a7ebd5a309 Merge branch 'master' of github.com:fireeye/capa into fix-507 2021-06-15 12:28:17 -06:00
William Ballenthin
d510840bb7 changelog 2021-06-15 12:26:37 -06:00
William Ballenthin
09ad0ec184 tests: save .viv by default, hopefully improve test performance 2021-06-15 12:24:29 -06:00
William Ballenthin
7f03db9fe4 main: dont save .viv by default, unless CAPA_SAVE_WORKSPACE set
closes #507
2021-06-15 12:24:01 -06:00
William Ballenthin
96b9bce93c Merge branch 'master' of github.com:fireeye/capa into fix-630 2021-06-15 11:59:25 -06:00
William Ballenthin
48858e114d main: refactor handling of rules, signatures cli arguments 2021-06-15 11:54:57 -06:00
William Ballenthin
1b4a087c4b render: don't stomp on meta dictionary
fixes a bug in bulk-process in which rules are evaluated multiple times
so meta cannot be updated in place.
2021-06-15 11:44:02 -06:00
William Ballenthin
6f1f928434 main: when --signatures provided, override default set
closes #630
2021-06-15 11:43:38 -06:00
Willi Ballenthin
efd02915ab Merge pull request #621 from fireeye/feature-447
add type annotations to public routines
2021-06-15 11:01:52 -06:00
William Ballenthin
9484fadd0f submodule sync data 2021-06-15 09:08:14 -06:00
Willi Ballenthin
b47b398b07 Merge pull request #636 from fireeye/fix-629
move test sigs into testfiles
2021-06-14 13:56:21 -06:00
Capa Bot
5867e880c6 Sync capa rules submodule 2021-06-14 19:41:57 +00:00
William Ballenthin
c1acf702b6 fixtures: move test sigs to testfiles 2021-06-14 11:37:39 -06:00
William Ballenthin
9a7c83b26f tests: move test sigs to testfiles 2021-06-14 11:36:53 -06:00
William Ballenthin
dd2671aac2 rules: fix types 2021-06-14 11:10:42 -06:00
William Ballenthin
c2981d5091 engine: cleanup some lints 2021-06-14 11:05:58 -06:00
William Ballenthin
ae2baebf6c import-to-bn: dont import * 2021-06-14 11:02:20 -06:00
William Ballenthin
7372aa91c6 engine: better type doc 2021-06-14 10:56:44 -06:00
William Ballenthin
48756a7621 ci: invoke mypy during testing 2021-06-14 10:41:53 -06:00
William Ballenthin
aca6ad2f52 scripts: fix types 2021-06-14 10:41:44 -06:00
William Ballenthin
24d61d8634 mypy: ignore more external deps 2021-06-14 10:41:32 -06:00
William Ballenthin
6411732bea rules: fix bug validating rules 2021-06-14 10:35:57 -06:00
William Ballenthin
152060a28a setup: move mypy deps in to capa[dev] target 2021-06-14 10:33:24 -06:00
William Ballenthin
919aef90c0 mypy: fix capa.features.common types 2021-06-14 10:33:08 -06:00
William Ballenthin
853d7285bd mypy: ignore ruamel 2021-06-14 10:32:51 -06:00
William Ballenthin
6842b92ca2 pep8 2021-06-14 10:25:37 -06:00
William Ballenthin
dba250ca86 rules: fix types and document description parsing 2021-06-14 10:25:15 -06:00
William Ballenthin
b8c524d2f5 type: capa.rules parse range 2021-06-14 10:09:35 -06:00
William Ballenthin
0ff5db9397 type: capa.rules feature validation 2021-06-14 10:06:48 -06:00
William Ballenthin
15334cf5d4 render: further refactor att&ck handling 2021-06-14 09:53:36 -06:00
William Ballenthin
f5cb5d462d render: further cleanup rendering of att&ck 2021-06-14 09:52:32 -06:00
William Ballenthin
79459d4a14 mypy fixes
type checker doesn't like a list that contains tuples with both
length 2 and length 3. so keep length constant with None values.
2021-06-14 09:50:12 -06:00
William Ballenthin
addd4683ca mypy fixes 2021-06-14 09:47:51 -06:00
William Ballenthin
6d8399684b type: capa.render 2021-06-14 09:28:33 -06:00
William Ballenthin
4583692539 type: capa.main 2021-06-14 09:19:08 -06:00
William Ballenthin
9b7e67443b extractors: fix type hints 2021-06-14 08:59:23 -06:00
William Ballenthin
83909b2be4 *: remove explicit object super class
closes #635
2021-06-14 08:47:09 -06:00
William Ballenthin
247d330f79 type: capa.features.extractors.base_extractor 2021-06-14 08:44:48 -06:00
Willi Ballenthin
1a31c84eef Merge pull request #632 from fireeye/dependabot/pip/black-21.6b0
build(deps-dev): bump black from 21.5b2 to 21.6b0
2021-06-14 08:20:53 -06:00
Willi Ballenthin
9ce92cfb5b Merge pull request #633 from fireeye/dependabot/pip/ruamel-yaml-0.17.9
build(deps): bump ruamel-yaml from 0.17.7 to 0.17.9
2021-06-14 08:20:31 -06:00
Willi Ballenthin
1f44a2dec8 Merge pull request #634 from fireeye/dependabot/pip/tqdm-4.61.1
build(deps): bump tqdm from 4.61.0 to 4.61.1
2021-06-14 08:20:19 -06:00
dependabot[bot]
b7cd467363 build(deps): bump tqdm from 4.61.0 to 4.61.1
Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.61.0 to 4.61.1.
- [Release notes](https://github.com/tqdm/tqdm/releases)
- [Commits](https://github.com/tqdm/tqdm/compare/v4.61.0...v4.61.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-14 05:59:01 +00:00
dependabot[bot]
ff3cc421eb build(deps): bump ruamel-yaml from 0.17.7 to 0.17.9
Bumps [ruamel-yaml](https://sourceforge.net/p/ruamel-yaml/code/ci/default/tree) from 0.17.7 to 0.17.9.

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-14 05:58:55 +00:00
dependabot[bot]
205798865d build(deps-dev): bump black from 21.5b2 to 21.6b0
Bumps [black](https://github.com/psf/black) from 21.5b2 to 21.6b0.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-14 05:58:48 +00:00
Capa Bot
10f499d230 Sync capa rules submodule 2021-06-14 03:51:14 +00:00
William Ballenthin
a21b53d737 Merge branch 'master' of github.com:fireeye/capa into feature-447 2021-06-10 09:02:39 -06:00
Capa Bot
0f15895b36 Sync capa rules submodule 2021-06-10 14:42:56 +00:00
Moritz
2ba2aec0d3 Merge pull request #624 from fireeye/fix-622
remove logic from __init__.py and break import cycles
2021-06-10 13:53:10 +02:00
William Ballenthin
11d50aa5b1 pep8 2021-06-10 00:29:23 -06:00
William Ballenthin
b066af9506 mypy: extend lib ignore config 2021-06-10 00:28:28 -06:00
William Ballenthin
059909c027 features: fix types 2021-06-10 00:23:01 -06:00
William Ballenthin
d61ff0c69f changelog 2021-06-10 00:08:00 -06:00
William Ballenthin
f6c2394bdf common: fix type annotations 2021-06-10 00:07:05 -06:00
William Ballenthin
df5ed6bbf2 indirect_calls: fix types 2021-06-10 00:02:23 -06:00
William Ballenthin
0b653aa47a ida: file: fix imports 2021-06-10 00:02:11 -06:00
William Ballenthin
b5a18de4a3 pep8 2021-06-09 23:52:15 -06:00
William Ballenthin
5408481606 type: capa.engine 2021-06-09 23:51:55 -06:00
William Ballenthin
1c66ebe638 type: capa.features.common 2021-06-09 23:47:06 -06:00
William Ballenthin
3e79dfd0e7 type: capa.rules 2021-06-09 23:39:07 -06:00
William Ballenthin
459df37b13 indirect_calls: fix typing circular dependencies 2021-06-09 23:28:05 -06:00
William Ballenthin
3d8edc513c type: capa.features.extractors.viv.insn 2021-06-09 23:24:51 -06:00
William Ballenthin
ab7bf53f67 type: capa.features.insn 2021-06-09 23:20:46 -06:00
William Ballenthin
c30a56bc11 type: capa.features.extractors.helpers 2021-06-09 23:19:36 -06:00
William Ballenthin
6918a039e9 type: capa.render.result_document 2021-06-09 23:15:45 -06:00
William Ballenthin
469e2ff870 type: capa.features.extractors.viv.basicblock 2021-06-09 23:12:07 -06:00
William Ballenthin
3416f7bc61 type: capa.features.file 2021-06-09 23:09:24 -06:00
William Ballenthin
a75d7576f8 type: capa.features.extractors.viv.indirect_calls 2021-06-09 23:08:29 -06:00
William Ballenthin
23addda29a type: capa.render.utils 2021-06-09 23:06:33 -06:00
William Ballenthin
14e2efa309 type: capa.features.extractors.viv.file 2021-06-09 23:01:14 -06:00
William Ballenthin
faa363cd8f type: capa.render.default 2021-06-09 22:59:54 -06:00
William Ballenthin
e29922af57 type: capa.features.extractors.pefile 2021-06-09 22:56:02 -06:00
William Ballenthin
8a0ae7ae55 type: capa.features.extractors.viv.helpers 2021-06-09 22:54:29 -06:00
William Ballenthin
6f67619621 type capa.features.freeze 2021-06-09 22:51:09 -06:00
William Ballenthin
3f55f678ca Merge branch 'fix-622' into feature-447 2021-06-09 22:41:10 -06:00
William Ballenthin
ee41d47e4d test_function_id: fix test imports 2021-06-09 22:35:26 -06:00
William Ballenthin
527e993bb4 engine: remove dependency on rules, fixing circular import 2021-06-09 22:30:43 -06:00
William Ballenthin
6b4d7266e6 changelog 2021-06-09 22:23:06 -06:00
William Ballenthin
954ed3a408 pep8 2021-06-09 22:22:03 -06:00
William Ballenthin
ac59e50b5f move capa/features/__init__.py logic to common.py
also cleanup imports across the board,
thanks to pylance.
2021-06-09 22:20:53 -06:00
William Ballenthin
7029ad32c4 move capa/features/extractors/__init__.py logic to base_extractor.py 2021-06-09 21:09:29 -06:00
William Ballenthin
766dcacdbe move logic out of capa/render/__init__.py 2021-06-09 18:06:51 -06:00
William Ballenthin
fc9ad6c737 move extractors/ida/__init__.py logic to extractor.py 2021-06-09 17:55:44 -06:00
William Ballenthin
7d2e664320 move extractors/smda/__init__.py logic to extractor.py 2021-06-09 17:52:06 -06:00
William Ballenthin
6187317a4e move extractors/viv/__init__.py logic to extractor.py 2021-06-09 17:49:50 -06:00
William Ballenthin
d81b0bcbfa move helpers/__init__.py to helpers.py 2021-06-09 17:43:58 -06:00
William Ballenthin
9c8e18acb4 pefile/__init__ to pefile.py 2021-06-09 17:42:46 -06:00
William Ballenthin
8aed58c1d4 *: remove __all__
closes #623
2021-06-09 17:38:57 -06:00
William Ballenthin
325c726f0e typing: capa.helpers 2021-06-09 15:09:37 -06:00
William Ballenthin
9a4e9b6586 setup: add initial mypy setup
invoke like: mypy --config-file .github/mypy/mypy.ini capa/main.py
2021-06-09 14:50:37 -06:00
Capa Bot
23354ec452 Sync capa rules submodule 2021-06-09 09:19:50 +00:00
Capa Bot
f698f4e79b Sync capa rules submodule 2021-06-09 08:08:12 +00:00
Moritz
c05a8bf910 Merge pull request #620 from fireeye/fix-619
correctly render negative numbers and offsets
2021-06-09 10:03:04 +02:00
Moritz
9ffbb82f4c Merge pull request #618 from fireeye/fix/616
fix 616
2021-06-09 10:00:04 +02:00
William Ballenthin
0508d31a35 changelog 2021-06-08 11:10:40 -06:00
William Ballenthin
901a398b31 insn: render negative number, offset correctly
closes #619
2021-06-08 11:09:32 -06:00
mike-hunhoff
fd0f87ca6e Update capa/features/file.py w/ PR changes
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2021-06-08 10:59:42 -06:00
Michael Hunhoff
84d2f9f324 fix 616 2021-06-08 10:16:54 -06:00
Capa Bot
f9bad7e5e4 Sync capa rules submodule 2021-06-08 14:17:39 +00:00
Capa Bot
40b6575db6 Sync capa-testfiles submodule 2021-06-08 12:48:33 +00:00
Willi Ballenthin
64d849aafc Merge pull request #613 from fireeye/doc/update-readme
update readme
2021-06-07 10:46:28 -06:00
Willi Ballenthin
3b6e6dcc00 Merge pull request #612 from fireeye/ci/no-changelog-dependabot
ignore dependabot for changelog check
2021-06-07 10:45:56 -06:00
Willi Ballenthin
d17ac2928f Merge pull request #615 from fireeye/bump-smda
bump smda and remove xfail
2021-06-07 10:33:21 -06:00
Moritz Raabe
8b58723f40 bump smda and remove xfail 2021-06-07 13:56:55 +02:00
Moritz Raabe
bed2e3777e job level exclusion 2021-06-07 12:38:03 +02:00
Capa Bot
c039e98d3f Sync capa rules submodule 2021-06-07 09:51:13 +00:00
Moritz Raabe
c3ba6a9025 update readme 2021-06-07 10:26:41 +02:00
Moritz
2691fb400e Merge pull request #611 from fireeye/dependabot/pip/pytest-cov-2.12.1
build(deps-dev): bump pytest-cov from 2.12.0 to 2.12.1
2021-06-07 09:55:12 +02:00
Moritz
e0075573d9 Merge pull request #610 from fireeye/dependabot/pip/ruamel-yaml-0.17.7
build(deps): bump ruamel-yaml from 0.17.5 to 0.17.7
2021-06-07 09:55:00 +02:00
Moritz
1bb8c78b60 Merge pull request #609 from fireeye/dependabot/pip/black-21.5b2
build(deps-dev): bump black from 21.5b1 to 21.5b2
2021-06-07 09:54:40 +02:00
Moritz Raabe
ff66346d2a ignore dependabot for changelog check 2021-06-07 09:52:46 +02:00
Capa Bot
6f51324cca Sync capa-testfiles submodule 2021-06-07 07:45:31 +00:00
Capa Bot
700259eab6 Sync capa rules submodule 2021-06-07 07:45:04 +00:00
Capa Bot
438677b129 Sync capa-testfiles submodule 2021-06-07 06:48:11 +00:00
Capa Bot
3f51e787e4 Sync capa rules submodule 2021-06-07 06:19:37 +00:00
Capa Bot
2bbf00d603 Sync capa rules submodule 2021-06-07 06:17:47 +00:00
Moritz
b21b041dab Merge pull request #608 from fireeye/fix-605
fix 605
2021-06-07 08:16:16 +02:00
Moritz
734b1702e6 Merge pull request #607 from Ana06/ahead-changed-files
Use Ana06/get-changed-files@v1.2
2021-06-07 08:11:27 +02:00
dependabot[bot]
a39e2e7e0f build(deps-dev): bump pytest-cov from 2.12.0 to 2.12.1
Bumps [pytest-cov](https://github.com/pytest-dev/pytest-cov) from 2.12.0 to 2.12.1.
- [Release notes](https://github.com/pytest-dev/pytest-cov/releases)
- [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-cov/compare/v2.12.0...v2.12.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-07 06:02:46 +00:00
dependabot[bot]
d9e1732766 build(deps): bump ruamel-yaml from 0.17.5 to 0.17.7
Bumps [ruamel-yaml](https://sourceforge.net/p/ruamel-yaml/code/ci/default/tree) from 0.17.5 to 0.17.7.

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-07 06:02:38 +00:00
dependabot[bot]
6dd5bbeffd build(deps-dev): bump black from 21.5b1 to 21.5b2
Bumps [black](https://github.com/psf/black) from 21.5b1 to 21.5b2.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-07 06:02:31 +00:00
William Ballenthin
3c4388e280 changelog 2021-06-04 11:48:03 -06:00
Ana Maria Martinez Gomez
6ffa5ef53e changelog: fix number of new rules
This was caused by a bug in the GH action which updates this number
automatically:
https://github.com/fireeye/capa-rules/pull/405
2021-06-04 19:47:57 +02:00
William Ballenthin
90ec848bf6 main: fix matching BB features at file scope
closes #605
2021-06-04 11:45:37 -06:00
William Ballenthin
e0be7f1b8e main: debug rules more correctly 2021-06-04 11:31:19 -06:00
Ana Maria Martinez Gomez
4ef3830b6b Use Ana06/get-changed-files@v1.2
Use Ana06/get-changed-files@v1.2 which removes the _head commit is ahead
of the base commit_ check. This made the action failed in not up-to-date
branches (in which rebasing is needed).

It supersedes https://github.com/fireeye/capa/pull/599
2021-06-04 14:03:41 +02:00
Ana María Martínez Gómez
e737595339 Merge pull request #604 from Ana06/lint_changelog
ci: lint CHANGELOG
2021-06-04 13:33:11 +02:00
Capa Bot
94cb090afe Sync capa rules submodule 2021-06-04 09:10:09 +00:00
Moritz
32e0a5dce2 Merge pull request #598 from fireeye/render/json-attck-fields
parse att&ck for output doc
2021-06-02 16:54:31 +02:00
Ana Maria Martinez Gomez
f304bdbd20 ci: lint CHANGELOG
The sync GH action in capa-rules relies on a single '- *$' in the
CHANGELOG file. Check in the tests that this is the case to avoid that
it is removed.

This happened in the following PR:
https://github.com/fireeye/capa/pull/591
This caused that the new rules in the following PR were not added to the
CHANGELOG:
https://github.com/fireeye/capa-rules/pull/400
2021-06-02 12:42:48 +02:00
Ana Maria Martinez Gomez
1a3286beda ci: fix CHANGELOG
The `-` used by the GitHub actions which updates the rules in the
CHANGELOG was removed in:
https://github.com/fireeye/capa/pull/591
Consequently the new rules added in the last pull request were not added
to the CHANGELOG:
https://github.com/fireeye/capa-rules/pull/400
2021-06-02 12:12:48 +02:00
Moritz Raabe
63cd70029f dedup code 2021-06-02 11:06:49 +02:00
Moritz Raabe
94089ff43f parse att&ck for output doc 2021-06-02 10:37:19 +02:00
Capa Bot
8f1ce68e96 Sync capa rules submodule 2021-06-01 17:51:43 +00:00
Willi Ballenthin
37208aabd3 Merge pull request #591 from fireeye/feature-590
main: use rule scope internal/limitation/file for file limitations, not code
2021-06-01 11:50:56 -06:00
Willi Ballenthin
8c3605c886 Merge branch 'master' into feature-590 2021-06-01 11:50:40 -06:00
William Ballenthin
2706a7171e linter: fix match namespace handling
closes #601
2021-06-01 11:38:05 -06:00
William Ballenthin
8f3d443247 rules: use existing code, dedup 2021-06-01 11:25:38 -06:00
Willi Ballenthin
9968d16f21 Merge pull request #593 from fireeye/feature-159
json: capture all strings matching regex
2021-06-01 11:18:08 -06:00
Willi Ballenthin
2756c05889 Merge branch 'master' into feature-159 2021-06-01 11:17:41 -06:00
William Ballenthin
8a65c565a5 pep8 2021-06-01 11:06:12 -06:00
William Ballenthin
17eeecc526 render: handle namespace matches in result document 2021-05-31 10:28:11 -06:00
William Ballenthin
3b245ea201 rules: index rules by namespace 2021-05-31 10:28:00 -06:00
William Ballenthin
3cd348e8f7 rules: implement __contains__ for RuleSet 2021-05-31 10:27:44 -06:00
William Ballenthin
6d08695b38 Merge branch 'master' of github.com:fireeye/capa into feature-590 2021-05-31 09:54:33 -06:00
William Ballenthin
66b2c07af4 main: show matching file limitation rule when showing warning 2021-05-31 09:53:19 -06:00
Capa Bot
b8a67553d0 Sync capa rules submodule 2021-05-31 08:53:38 +00:00
Moritz
82eae4324e Merge pull request #595 from fireeye/dependabot/pip/ruamel-yaml-0.17.5
build(deps): bump ruamel-yaml from 0.17.4 to 0.17.5
2021-05-31 10:39:33 +02:00
Moritz
ac9c132c91 Merge pull request #594 from fireeye/dependabot/pip/tqdm-4.61.0
build(deps): bump tqdm from 4.60.0 to 4.61.0
2021-05-31 10:39:14 +02:00
Moritz
c2953b9733 Merge pull request #576 from fireeye/render/json-mbc-attck-fields
render `rule.meta.mbc` on output
2021-05-31 10:38:27 +02:00
Moritz
30de93b81f Merge pull request #596 from fireeye/tests/fix-smda-fails
fix smda test xfail
2021-05-31 10:37:43 +02:00
Moritz Raabe
e6f45b63d6 fix test xfail 2021-05-31 10:02:31 +02:00
dependabot[bot]
c1b689a375 build(deps): bump ruamel-yaml from 0.17.4 to 0.17.5
Bumps [ruamel-yaml](https://sourceforge.net/p/ruamel-yaml/code/ci/default/tree) from 0.17.4 to 0.17.5.

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-31 05:57:42 +00:00
dependabot[bot]
c1546cf6a8 build(deps): bump tqdm from 4.60.0 to 4.61.0
Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.60.0 to 4.61.0.
- [Release notes](https://github.com/tqdm/tqdm/releases)
- [Commits](https://github.com/tqdm/tqdm/compare/v4.60.0...v4.61.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-31 05:57:33 +00:00
Moritz Raabe
de96bb763b address code review 2021-05-28 16:52:17 +02:00
Moritz Raabe
9e62bd1b24 update renderers 2021-05-28 16:40:15 +02:00
Moritz Raabe
54d21a043e parse mbc for result doc 2021-05-28 16:40:15 +02:00
Moritz Raabe
f593592ff0 parse mbc fields 2021-05-28 16:40:15 +02:00
Willi Ballenthin
ed02088c82 detect (and short circuit) file limitations at file scope (#586)
* smda: move pe carve into helpers

* smda: simplify test parametrization/xfail

* extractors: add pefile extractor for file scope features

* pep8

* main: bail early on file limitation detected at file scope

closes #583

* changelog
2021-05-28 08:14:44 -06:00
Ana María Martínez Gómez
b3fff51002 Merge pull request #584 from Ana06/changelog-GA
ci: Reject PRs without CHANGELOG update
2021-05-28 12:09:06 +02:00
Ana Maria Martinez Gomez
51884fea2d doc: Fix link and add more details
Fix broken link to `pull_request_template.md` and add some more details.

Related #457
2021-05-28 12:07:21 +02:00
Ana Maria Martinez Gomez
84b0bc6439 changelog: Add #584 to CHANGELOG 2021-05-28 11:08:05 +02:00
Ana Maria Martinez Gomez
38d41e2f59 ci: fix get-changed-files
Ana06/get-changed-files@v1.1 is a fork of
https://github.com/jitterbit/get-changed-files, which supports
`pull_request_target` and allow to filter files using regular
expressions.

As we need to use `pull_request_target`, Ana06/get-changed-files@v1.1
works, but jitterbit/get-changed-files@v1 doesn't.
2021-05-28 11:08:04 +02:00
Ana Maria Martinez Gomez
23ff9e719f ci: only reject once and fix dismiss
`Ana06/automatic-pull-request-review@v0.1.0` is a fork of
https://github.com/AndrewMusgrave/automatic-pull-request-review which
fixes `DISMISS` and provides an `allow_duplicate` option which allows to
only approve once.
2021-05-28 11:08:04 +02:00
Ana Maria Martinez Gomez
7a0a6f9cf1 ci: check changelog
Request changes in a PR without CHANGELOG update.
2021-05-28 11:08:04 +02:00
Ana Maria Martinez Gomez
f6960e4deb github: Improve pull request template
After using the PR template for a while, I think simplifying it will be
helpful:

- GitHub includes the commit message description automatically with the
aim of saving you time as it is sometimes also a good PR description.
With the current template, I need to cut this test and paste it into the
description section (which is really annoying!).

- Make a single simpler checklist. Add information as comment and have a
straightforward list which helps us remembering the changelog, tests and
documentation without needing to invest much time. The changelog
bulletpoint will also be used in GitHub Actions.
2021-05-28 11:08:00 +02:00
Willi Ballenthin
bd63ded1dd file scope API features (#568)
* smda: minor unrelated fixes

* file features: extract API features at file scope for library functions

closes #567

* changelog

* ida: add file-scope API feature

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

* fix lints from pylance

* features: use "function-name" for recognized linked functions

* pep8

* pep8

* rules: remove incorrect feature scope

* tests: xfail SMDA tests relying on function id

* tests: fixtures: order tests by sample, ideally improving memory usage

* pep8

* pep8

* smda: xfail two more tests

Co-authored-by: mike-hunhoff <mike.hunhoff@gmail.com>
2021-05-27 12:59:00 -06:00
William Ballenthin
3c90e909a1 pep8 2021-05-27 10:45:01 -06:00
William Ballenthin
70396ffa36 ida: try to fix regex match rendering 2021-05-27 10:38:40 -06:00
William Ballenthin
56efb2adfe changelog 2021-05-27 10:28:41 -06:00
William Ballenthin
868b5ed6a3 features: extract all strings matching regex
closes #159
2021-05-27 10:27:39 -06:00
William Ballenthin
0a226e8b01 main: use rule scope internal/limitation/file for file limitations, not
code

closes #390
2021-05-27 09:18:55 -06:00
Capa Bot
7df29b491c Sync capa-testfiles submodule 2021-05-27 07:08:00 +00:00
Capa Bot
f0fb5fb346 Sync capa rules submodule 2021-05-26 21:03:50 +00:00
Capa Bot
342497b72f Sync capa rules submodule 2021-05-26 07:31:49 +00:00
Capa Bot
2b19257c5c Sync capa-testfiles submodule 2021-05-26 07:22:40 +00:00
Moritz
4ebbdcd00c Merge pull request #582 from fireeye/ci/lint-color-optional
or/optional lint and colors
2021-05-25 17:26:23 +02:00
Moritz Raabe
204d8b36df add or/optional lint and colors
closes #348
2021-05-25 16:32:47 +02:00
Moritz Raabe
8e4e9fc616 Revert "Sync capa-testfiles submodule"
This reverts commit 826d472c07.
2021-05-25 14:58:01 +02:00
Capa Bot
826d472c07 Sync capa-testfiles submodule 2021-05-25 12:45:59 +00:00
Capa Bot
57f416d62d Sync capa-testfiles submodule 2021-05-25 12:44:13 +00:00
Capa Bot
a79a547682 Sync capa rules submodule 2021-05-24 15:25:44 +00:00
Capa Bot
bd9812cee4 Sync capa rules submodule 2021-05-24 15:22:21 +00:00
Willi Ballenthin
2a36894d85 Merge pull request #578 from fireeye/dependabot/pip/viv-utils-flirt--0.6.4
build(deps): bump viv-utils[flirt] from 0.6.2 to 0.6.4
2021-05-24 09:14:31 -06:00
Willi Ballenthin
c33c4c45dc Merge pull request #577 from fireeye/dependabot/pip/smda-1.5.17
build(deps): bump smda from 1.5.14 to 1.5.17
2021-05-24 09:14:22 -06:00
dependabot[bot]
9cd07a0cee build(deps): bump viv-utils[flirt] from 0.6.2 to 0.6.4
Bumps [viv-utils[flirt]](https://github.com/williballenthin/viv-utils) from 0.6.2 to 0.6.4.
- [Release notes](https://github.com/williballenthin/viv-utils/releases)
- [Commits](https://github.com/williballenthin/viv-utils/compare/v0.6.2...v0.6.4)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-24 06:03:22 +00:00
dependabot[bot]
4f85d85ea6 build(deps): bump smda from 1.5.14 to 1.5.17
Bumps [smda](https://github.com/danielplohmann/smda) from 1.5.14 to 1.5.17.
- [Release notes](https://github.com/danielplohmann/smda/releases)
- [Commits](https://github.com/danielplohmann/smda/commits)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-24 06:03:15 +00:00
Willi Ballenthin
8699003597 Merge pull request #572 from fireeye/feature-571
linter: summarize status at end
2021-05-21 11:14:29 -06:00
Willi Ballenthin
4cada67b21 Merge branch 'master' into feature-571 2021-05-21 11:14:22 -06:00
Willi Ballenthin
0a203b54cd changelog 2021-05-21 11:13:48 -06:00
Willi Ballenthin
cf1e9dc425 Merge pull request #573 from fireeye/lazy-import-flirt
lazy import flirt
2021-05-21 09:50:14 -06:00
Willi Ballenthin
6b8bb0520d Merge pull request #575 from ruppde/master
Update capa2yara.py
2021-05-21 09:45:24 -06:00
Arnim Rupp
7759d2dd79 Update capa2yara.py 2021-05-21 17:04:16 +02:00
Arnim Rupp
73f121cf03 Update capa2yara.py
bugfix: https://github.com/fireeye/capa-rules/blob/master/collection/get-geographical-location.yml hit an far too many files with /\bcity opposed to the intention of the capa rule ti just hit in function names. changed to /\x00city.
2021-05-21 16:51:14 +02:00
Moritz
91f914f5c0 Merge pull request #562 from fireeye/lib-meta-info
improve progress bar output
2021-05-21 16:47:52 +02:00
Moritz Raabe
af5613250f lazy import flirt
closes #540
2021-05-21 11:31:37 +02:00
Capa Bot
72da8f3aed Sync capa rules submodule 2021-05-21 07:12:57 +00:00
Moritz Raabe
a8e353fe31 revert rule loading pbar 2021-05-20 14:00:01 +02:00
Moritz Raabe
8a386b6909 improve progress bar output 2021-05-20 13:56:29 +02:00
Ana Maria Martinez Gomez
83606bbc0f changelog: convert capa rules to YARA rules
Add https://github.com/fireeye/capa/pull/561 to CHANGELOG.
2021-05-20 11:25:24 +02:00
Moritz
caaeded278 Merge pull request #563 from fireeye/ci/lint-statement-children
lint statements for single child statements
2021-05-20 10:41:41 +02:00
Willi Ballenthin
dcf4a056ee show-features: skip library functions (#570)
* show-features: skip library functions

closes #569

* changelog
2021-05-20 10:34:48 +02:00
Capa Bot
f9cec64c2d Sync capa-testfiles submodule 2021-05-20 08:11:28 +00:00
William Ballenthin
9b1400c23a pep8 2021-05-19 16:14:37 -06:00
William Ballenthin
60d77759f2 Merge branch 'feature-571' of github.com:fireeye/capa into feature-571 2021-05-19 16:14:09 -06:00
Willi Ballenthin
5fc705856d Merge branch 'master' into feature-571 2021-05-20 16:40:37 -06:00
William Ballenthin
0a1adb99e0 lint: cleanup handling of nursery rules further 2021-05-19 16:13:45 -06:00
William Ballenthin
3eef034a94 lint: better handling of nursery rule summary 2021-05-19 16:06:07 -06:00
Capa Bot
66d96201cb Sync capa rules submodule 2021-05-19 20:31:48 +00:00
Moritz Raabe
586726fb13 lint statements for single child statements 2021-05-19 18:25:14 +02:00
Capa Bot
656cdfc41c Sync capa rules submodule 2021-05-19 16:21:47 +00:00
Arnim Rupp
7b62b589f7 Create capa2yara.py (#561)
* Create capa2yara.py

* Update capa2yara.py

    isort --profile black --length-sort --line-width 120

    black -l 120

* Update scripts/capa2yara.py

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

Co-authored-by: Arnim Rupp <46819580+2d4d@users.noreply.github.com>
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2021-05-19 18:01:04 +02:00
Capa Bot
e7884c9a53 Sync capa rules submodule 2021-05-19 07:50:11 +00:00
William Ballenthin
2f2849dee0 changelog 2021-05-18 15:20:54 -06:00
William Ballenthin
ff88393248 linter: summarize status at end
closes #571
2021-05-18 15:19:34 -06:00
William Ballenthin
9ed6e12e7c Merge branch 'master' of github.com:fireeye/capa 2021-05-18 13:35:59 -06:00
William Ballenthin
ec5cec619d rules: add tests demonstrating mnemonic descriptions 2021-05-18 13:35:24 -06:00
Capa Bot
760867b81e Sync capa rules submodule 2021-05-17 15:00:45 +00:00
Capa Bot
abeaac0675 Sync capa rules submodule 2021-05-17 10:14:49 +00:00
Moritz
010866a3bd Merge pull request #560 from fireeye/dependabot/pip/pytest-cov-2.12.0
build(deps-dev): bump pytest-cov from 2.11.1 to 2.12.0
2021-05-17 12:14:16 +02:00
Capa Bot
8f9f792930 Sync capa rules submodule 2021-05-17 08:36:26 +00:00
Capa Bot
9ccdce9896 Sync capa rules submodule 2021-05-17 08:35:45 +00:00
dependabot[bot]
0dc212f53e build(deps-dev): bump pytest-cov from 2.11.1 to 2.12.0
Bumps [pytest-cov](https://github.com/pytest-dev/pytest-cov) from 2.11.1 to 2.12.0.
- [Release notes](https://github.com/pytest-dev/pytest-cov/releases)
- [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-cov/compare/v2.11.1...v2.12.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-17 05:57:20 +00:00
Capa Bot
3cf4a47773 Sync capa rules submodule 2021-05-12 14:23:14 +00:00
Capa Bot
bbf59d65ad Sync capa rules submodule 2021-05-12 12:14:30 +00:00
Moritz
6b738f754e Merge pull request #557 from fireeye/dependabot/pip/black-21.5b1
build(deps-dev): bump black from 21.4b2 to 21.5b1
2021-05-12 07:35:43 +02:00
dependabot[bot]
83a4e054d1 build(deps-dev): bump black from 21.4b2 to 21.5b1
Bumps [black](https://github.com/psf/black) from 21.4b2 to 21.5b1.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/commits)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-11 17:42:03 +00:00
Moritz
9843776460 Merge pull request #552 from fireeye/dependabot/pip/pytest-6.2.4
build(deps-dev): bump pytest from 6.2.3 to 6.2.4
2021-05-11 19:40:43 +02:00
dependabot[bot]
2626572ddc build(deps-dev): bump pytest from 6.2.3 to 6.2.4
Bumps [pytest](https://github.com/pytest-dev/pytest) from 6.2.3 to 6.2.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/6.2.3...6.2.4)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-11 16:51:23 +00:00
Moritz
e3af23f209 Merge pull request #551 from fireeye/dependabot/pip/vivisect-1.0.3
build(deps): bump vivisect from 1.0.1 to 1.0.3
2021-05-11 18:48:16 +02:00
dependabot[bot]
0f16787ef9 build(deps): bump vivisect from 1.0.1 to 1.0.3
Bumps [vivisect](https://github.com/vivisect/vivisect) from 1.0.1 to 1.0.3.
- [Release notes](https://github.com/vivisect/vivisect/releases)
- [Changelog](https://github.com/vivisect/vivisect/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/vivisect/vivisect/compare/v1.0.1...v1.0.3)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-11 15:01:03 +00:00
Moritz
495a270c99 Update CHANGELOG.md 2021-05-11 16:32:54 +02:00
Moritz
424a25cb91 Fix tests on Windows - reduced memory impact and general fixes (#545)
* Update tests.yml

* Update .github/workflows/tests.yml

* Update tests.yml

* update

* min tests

* enable all, no sigpaths

* update cache

* save workspace, log caching

* updated tests

* update tests

* update rec call test

* lower cache size

* address Ana's feedback
2021-05-11 16:29:01 +02:00
Capa Bot
fa0809685e Sync capa rules submodule 2021-05-11 11:10:45 +00:00
Ana Maria Martinez Gomez
188966a94b changelog: support multiple authors
GH didn't support multiple authors, producing a breaking entry in the
last update. Correct the entry and mention the fix in the CHANGELOG.

https://github.com/fireeye/capa/issues/555
2021-05-11 12:48:30 +02:00
Capa Bot
d7b7e0111e Sync capa rules submodule 2021-05-10 08:24:40 +00:00
Capa Bot
be11223e4b Sync capa rules submodule 2021-05-07 15:06:52 +00:00
Ana Maria Martinez Gomez
2cbf5147c0 changelog: add #517 and capa/rules/374
Add to the changelog that we now update `New Rules` section in CHANGELOG
automatically.
2021-05-07 17:01:55 +02:00
Capa Bot
5b026df5f4 Sync capa rules submodule 2021-05-07 14:47:03 +00:00
Ana María Martínez Gómez
ac842c95d3 Merge pull request #549 from Ana06/changelog
Update CHANGELOG and release
2021-05-07 16:34:08 +02:00
Capa Bot
aaaeec4de7 Sync capa rules submodule 2021-05-07 13:54:11 +00:00
Capa Bot
99a7380faf Sync capa-testfiles submodule 2021-05-07 12:49:58 +00:00
Ana Maria Martinez Gomez
f43ffabded doc: add item to release checklist
We should update capa everywhere after releasing!
2021-05-07 12:55:02 +02:00
Ana Maria Martinez Gomez
52c0cfd5d0 changelog: prepare to automatize new rules entries
Use an empty item in the `New Rules` section as a marker for the GitHub
Action. If this causes problems, we could look into other solution such
as writing 2 lines before `### Bug Fixes`. But I think this is the
easiest I can come up with. So lets give it a try.
2021-05-07 12:55:02 +02:00
Ana Maria Martinez Gomez
1caf4a7fbf changelog: add missing changes
Add missing changes to CHANGELOG. It should be up-to-date now, with the
exception of the dependencies updates which I think need discussion.
2021-05-07 12:54:59 +02:00
Ana Maria Martinez Gomez
98a976fa72 changelog: add v1.6.3
Add v1.6.3 release which backports IDA 7.6 support to Python 2. Also
remove the capa-rules raw diff as there are not changes (and the tag
doesn't exist).
2021-05-06 23:25:53 +02:00
Capa Bot
3a883807e5 Sync capa rules submodule 2021-05-06 18:07:01 +00:00
Capa Bot
b1b34db0b6 Sync capa rules submodule 2021-05-04 13:43:40 +00:00
Capa Bot
4901cd1da1 Sync capa-testfiles submodule 2021-05-04 07:26:14 +00:00
Capa Bot
272471e158 Sync capa rules submodule 2021-05-03 22:42:41 +00:00
William Ballenthin
8f0ce11ff6 tests: register common FLIRT sigs
closes #538
2021-05-01 08:06:56 -06:00
Willi Ballenthin
e8c807b993 Merge pull request #541 from fireeye/dependabot/pip/black-21.4b2
build(deps-dev): bump black from 21.4b0 to 21.4b2
2021-05-03 08:35:32 -06:00
dependabot[bot]
0b1c80d4d5 build(deps-dev): bump black from 21.4b0 to 21.4b2
Bumps [black](https://github.com/psf/black) from 21.4b0 to 21.4b2.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/master/CHANGES.md)
- [Commits](https://github.com/psf/black/commits)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-03 06:36:46 +00:00
Capa Bot
82ce223c9b Sync capa-testfiles submodule 2021-04-30 21:06:56 +00:00
Capa Bot
f190b630b7 Sync capa-testfiles submodule 2021-04-30 21:06:48 +00:00
Capa Bot
614a6caee6 Sync capa rules submodule 2021-04-30 21:05:57 +00:00
Capa Bot
ddda87373d Sync capa rules submodule 2021-04-30 20:35:46 +00:00
Capa Bot
9ceebb9bb2 Sync capa-testfiles submodule 2021-04-30 17:13:44 +00:00
Willi Ballenthin
7d2bb6f61b changelog: document FLIRT #446 2021-04-30 08:54:32 -06:00
Willi Ballenthin
c7fe132389 Merge pull request #446 from fireeye/function-id-flirt
feature: match functions with FLIRT
2021-04-30 08:49:30 -06:00
William Ballenthin
404c7a7e88 tests: fix function id tests 2021-04-30 08:48:49 -06:00
William Ballenthin
9a2827935f sigs: add README with license 2021-04-30 08:45:41 -06:00
William Ballenthin
55b83fc2b5 tests: re-enable function id test 2021-04-30 08:37:38 -06:00
William Ballenthin
b89a29b997 freeze: use common args 2021-04-30 08:35:46 -06:00
Moritz
5aa7c57798 Merge pull request #536 from Ana06/ida7_6sp1
doc: document IDA 7.6sp1
2021-04-29 11:05:42 +02:00
Ana Maria Martinez Gomez
e46d1bbbfb doc: document IDA 7.6sp1
The Service Pack 1 for IDA 7.6 includes a bug fix that broke capa
explorer. Document this as an alternative to install the patch.
2021-04-29 11:00:12 +02:00
William Ballenthin
14abb7d4f6 pep8 2021-04-27 13:41:59 -06:00
William Ballenthin
b0c27f5890 setup: bump viv-utils dep v0.6.2 2021-04-27 13:29:45 -06:00
William Ballenthin
bd92933030 show-features: accept signatures or use default 2021-04-27 13:27:59 -06:00
William Ballenthin
249332a9dd lint: load default sigs 2021-04-27 13:22:45 -06:00
William Ballenthin
1a99ff8ccb main: remove old code 2021-04-27 13:12:39 -06:00
William Ballenthin
7373437317 pep8 2021-04-27 13:12:20 -06:00
William Ballenthin
4e7364f25b main: import flirt at top level 2021-04-27 13:11:05 -06:00
William Ballenthin
ce9fd73fa9 main: further document not analyzing workspace 2021-04-27 13:09:52 -06:00
William Ballenthin
9ca1a7ebb6 extractors: do cast-to-int correctly 2021-04-27 13:07:27 -06:00
William Ballenthin
e8457c7abf Merge branch 'function-id-flirt' of github.com:fireeye/capa into function-id-flirt 2021-04-27 12:34:26 -06:00
William Ballenthin
f4ba5a5eb9 setup: bump viv-utils 0.6.1 for more platform support 2021-04-27 12:33:44 -06:00
Moritz Raabe
fc126451a7 add signature files 2021-04-27 19:27:02 +02:00
William Ballenthin
89ad582af5 main: flirt: pat: ensure posix-style line endings 2021-04-27 11:05:21 -06:00
Capa Bot
e66d74764a Sync capa rules submodule 2021-04-27 15:02:51 +00:00
William Ballenthin
4962fcfcde ci: fix accidental merge conflict 2021-04-26 12:19:25 -06:00
William Ballenthin
582e45f72f Merge branch 'function-id-flirt' of github.com:fireeye/capa into function-id-flirt 2021-04-26 12:14:44 -06:00
William Ballenthin
6ec89baf26 pep8 2021-04-26 12:12:51 -06:00
William Ballenthin
76cd530a0f flirt: py3 2021-04-26 12:11:59 -06:00
William Ballenthin
f6a105bcc1 pep8 2021-04-26 12:09:39 -06:00
William Ballenthin
75eed82d33 main: clarify that get_workspace caller is responsible for saving 2021-04-26 12:08:20 -06:00
Capa Bot
fbe307d26a Sync capa rules submodule 2021-04-26 16:20:38 +00:00
Capa Bot
c4a0c3d54a Sync capa rules submodule 2021-04-26 16:18:28 +00:00
William Ballenthin
c79f461e39 Merge branch 'master' into function-id-flirt 2021-04-26 09:47:42 -06:00
Capa Bot
24cd301fa8 Sync capa-testfiles submodule 2021-04-26 14:53:44 +00:00
Willi Ballenthin
a32d609ead Merge pull request #534 from fireeye/dependabot/pip/black-21.4b0
build(deps-dev): bump black from 20.8b1 to 21.4b0
2021-04-26 08:45:10 -06:00
William Ballenthin
a0e045dc52 ci: use black/isort dep from setup.py
closes #535
2021-04-26 08:39:01 -06:00
William Ballenthin
3111593ab8 pep8 2021-04-26 08:34:36 -06:00
Capa Bot
75d9ff5fff Sync capa rules submodule 2021-04-26 12:26:25 +00:00
dependabot[bot]
42877b0b6e build(deps-dev): bump black from 20.8b1 to 21.4b0
Bumps [black](https://github.com/psf/black) from 20.8b1 to 21.4b0.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/master/CHANGES.md)
- [Commits](https://github.com/psf/black/commits)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-26 06:30:28 +00:00
Capa Bot
f54b697187 Sync capa rules submodule 2021-04-23 22:50:16 +00:00
Capa Bot
e4a001170c Sync capa-testfiles submodule 2021-04-23 22:49:23 +00:00
Willi Ballenthin
bb15023b0b Merge pull request #533 from fireeye/mr-tz-patch-1
Update installation.md
2021-04-22 14:40:55 -06:00
Moritz
54531ebf35 Update installation.md 2021-04-22 20:41:07 +02:00
Capa Bot
9257e326f3 Sync capa-testfiles submodule 2021-04-22 18:04:58 +00:00
Capa Bot
b59b83a86a Sync capa-testfiles submodule 2021-04-22 17:39:19 +00:00
Capa Bot
caec649a5d Sync capa rules submodule 2021-04-16 14:23:56 +00:00
Capa Bot
09d0286b1b Sync capa rules submodule 2021-04-14 18:35:29 +00:00
Ana María Martínez Gómez
1ebe9766c0 Merge pull request #530 from Ana06/masterv1-6-2
changelog: add v1.6.2
2021-04-14 10:44:57 +02:00
Capa Bot
3e3b1579c3 Sync capa rules submodule 2021-04-14 06:23:30 +00:00
Ana Maria Martinez Gomez
ec6b380acd changelog: add v1.6.2
The code of v1.6.2 is not included in the `master` branch, as it was
backported to `master-py2`. But users may expect to find all releases in
the CHANGELOG of the master branch.
2021-04-13 17:27:48 +02:00
Willi Ballenthin
5ceb515325 Merge pull request #528 from fireeye/williballenthin-patch-2
explorer: readme: document IDA 7.6 patch
2021-04-13 08:54:59 -06:00
Willi Ballenthin
8938744e3e Merge pull request #497 from fireeye/williballenthin-patch-1
ida: support 7.6
2021-04-13 08:54:51 -06:00
Willi Ballenthin
d0f6b47f58 changelog: #528 2021-04-13 08:35:10 -06:00
Willi Ballenthin
a07bcbff2e explorer: readme: document IDA 7.6 patch
closes #496
2021-04-13 08:33:37 -06:00
Moritz
3023634536 build using Py3.8 and test across more OSs (#506)
* build using Py3.8 and test across more OSs

* enable for release

* test builds on push to master
2021-04-13 15:42:58 +02:00
Moritz
a11d04e92b Merge pull request #525 from fireeye/dependabot/pip/smda-1.5.14
build(deps): bump smda from 1.5.13 to 1.5.14
2021-04-12 14:13:36 +02:00
dependabot[bot]
2140a3d762 build(deps): bump smda from 1.5.13 to 1.5.14
Bumps [smda](https://github.com/danielplohmann/smda) from 1.5.13 to 1.5.14.
- [Release notes](https://github.com/danielplohmann/smda/releases)
- [Commits](https://github.com/danielplohmann/smda/commits)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-12 06:32:25 +00:00
Willi Ballenthin
1f6debc6e0 Merge pull request #524 from fireeye/mr-tz-patch-1
Update pull_request_template.md
2021-04-09 15:03:13 -06:00
Moritz
eb5c705083 Update pull_request_template.md 2021-04-09 15:03:43 +02:00
Capa Bot
f01044e453 Sync capa rules submodule 2021-04-09 11:19:42 +00:00
Moritz
8ef3eb85a2 Merge pull request #523 from fireeye/auto-detect-sc-extension-2
move auto format check
2021-04-09 13:16:12 +02:00
Moritz Raabe
d1cd4ef259 move auto format check 2021-04-09 11:59:30 +02:00
Capa Bot
a8bef0d9c0 Sync capa rules submodule 2021-04-09 09:21:00 +00:00
Moritz
309a9abb8a Merge pull request #521 from fireeye/auto-detect-sc-extension
auto detect shellcode file extensions
2021-04-09 11:13:25 +02:00
Moritz
cc13a7681a Merge pull request #522 from fireeye/explorer/update-docs
updating capa explorer doc
2021-04-09 10:31:03 +02:00
Michael Hunhoff
503a723611 updating capa explorer doc 2021-04-08 14:06:23 -06:00
Moritz Raabe
998f4a6bad auto detect shellcode file extensions 2021-04-08 18:49:22 +02:00
Willi Ballenthin
1be3613063 changelog: describe #519 2021-04-08 09:10:14 -06:00
Willi Ballenthin
9ffbe5cd76 Merge pull request #519 from fireeye/dependabot/pip/ruamel-yaml-0.17.4
build(deps): bump ruamel-yaml from 0.17.0 to 0.17.4
2021-04-08 09:06:14 -06:00
Ana María Martínez Gómez
255d6ea176 Merge pull request #517 from Ana06/better-tag
ci: add capa release link to capa-rules tag
2021-04-08 10:49:07 +02:00
dependabot[bot]
628e2ef3f4 build(deps): bump ruamel-yaml from 0.17.0 to 0.17.4
Bumps [ruamel-yaml](https://sourceforge.net/p/ruamel-yaml/code/ci/default/tree) from 0.17.0 to 0.17.4.

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-08 08:49:03 +00:00
Ana María Martínez Gómez
64465a7a31 Merge pull request #480 from Ana06/py3-only 2021-04-08 10:48:15 +02:00
Ana Maria Martinez Gomez
9d79baa96a ci: add capa release link to capa-rules tag
GitHub displays the commit's message of the tag if no description is
given, which is ugly. Use annotated tags which include a message. Use
the release link as message, as this is useful information.
2021-04-07 18:46:51 +02:00
Ana Maria Martinez Gomez
3013269a1c changelog: Update changelog
Add `drop Python 2 support` entry.
2021-04-07 18:24:52 +02:00
Ana Maria Martinez Gomez
bbff3016fe doc: Update Python 2 related documentation
Update documentation and code comments which mention Python 2.
2021-04-07 18:20:08 +02:00
Ana Maria Martinez Gomez
e9d190799e py3: use Python 3.6 to publish capa 2021-04-07 18:20:08 +02:00
Ana Maria Martinez Gomez
0465333aa4 py3: Python 3 knows about cp65001
Python 2 doesn't know about `cp65001`. But Python 3 does. Since Python
3.8 `cp65001` is an alias to `utf_8`. But not before Python 3.8 and it
used to cause some problems:
https://bugs.python.org/issue36778
Keep this code to ensure same behavior for all Python versions.
2021-04-07 18:20:08 +02:00
Ana Maria Martinez Gomez
28406dafa1 py3: codecs.decode doesn't raise TypeError
`codecs.decode` doesn't raise `TypeError` in Python 3. Just obey the
comment!
2021-04-07 18:20:08 +02:00
Ana Maria Martinez Gomez
73a49c6a1f py3: remove rstrip("L") needed in Python 2
In Python 3, long integers are not formatted with a trailing `L`, so
this code is not longer needed.
2021-04-07 18:20:08 +02:00
Ana Maria Martinez Gomez
4028171f59 py3: use python3 in shebang 2021-04-07 18:20:08 +02:00
Ana Maria Martinez Gomez
5d341ba078 py3: remove six
As we are not supporting Python 2 any longer, we can stop using six and
use the equivalent Python 3 method instead.
2021-04-07 18:20:07 +02:00
Ana Maria Martinez Gomez
dfb7cf4888 py3: set and document env
Document how to use env now that we are Python3 only. Adapt
`scripts/ci.sh`.
2021-04-07 18:20:07 +02:00
Ana Maria Martinez Gomez
d640c57e29 py3: Update setup.py
Require Python 3.6+ in setup.py
2021-04-07 18:20:07 +02:00
Ana Maria Martinez Gomez
c0d6468347 py3: Remove Python 2 tests
Tests don't need to support Python 2 any longer. Do not run tests with
Python 2.
2021-04-07 18:20:07 +02:00
Ana Maria Martinez Gomez
058b61b10c py3: prevent that capa is run with Python2
Raise an exception from main if capa is run with Python < 3.6 to avoid
any silly issues reported to GitHub.
2021-04-07 18:20:07 +02:00
Ana Maria Martinez Gomez
aa4d6305af py3: remove py2/3 branches
Remove `if-else`s with a condition like `sys.version_info >= (3, 0)`.
2021-04-07 18:20:06 +02:00
Ana María Martínez Gómez
407ecab162 Merge pull request #515 from Ana06/v1-6-1 2021-04-07 18:03:56 +02:00
Ana Maria Martinez Gomez
cbc1f57b21 changelog: add master (unreleased) to CHANGELOG
Add placeholder for master (unreleased changes) in CHANGELOG. Document
this in the release checklist.
2021-04-07 17:50:19 +02:00
Ana Maria Martinez Gomez
374a9e4337 changelog: v1.6.1
This release includes several bug fixes, such as a vivisect fix for a bug, which caused that capa didn't work on Windows with Python 3. It also adds 17 new rules and a bunch of improvements in the rules and IDA rule generator. We appreciate everyone who opened issues, provided feedback, and contributed code and rules.

This is the very last capa release that supports Python 2.
2021-04-07 17:50:16 +02:00
Capa Bot
83e2f80d10 Sync capa-testfiles submodule 2021-04-07 13:53:32 +00:00
Ana Maria Martinez Gomez
576211c4ef version: bump to v1.6.1 2021-04-07 11:11:43 +02:00
Ana María Martínez Gómez
31fc5a31d6 Merge pull request #513 from Ana06/ping-dependencies
setup: pin dependencies
2021-04-07 10:19:04 +02:00
Ana Maria Martinez Gomez
eb08943d4f setup: pin dependencies
Pin all dependencies in setup to the currently used version to avoid
that a new release breaks capa without being noticed.

Closes https://github.com/fireeye/capa/issues/498
2021-04-07 09:40:13 +02:00
Ana María Martínez Gómez
c36ed71353 Merge pull request #470 from fireeye/ci/test-windows 2021-04-07 09:38:34 +02:00
Ana Maria Martinez Gomez
fa52dbcf84 ci: skip smda tests in win32
Due to a bug, two `test_smda_features` tests are failing:
https://github.com/danielplohmann/smda/issues/20

Disable them until the bug is fixed.
2021-04-06 21:53:22 +02:00
Ana Maria Martinez Gomez
d412e66cea ci: do not test Python 2.7 with Windows
The Python 2.7 tests fail in Windows with vivisect because the Windows
filesystem encoding is not UTF-8. This shouldn't be a problem when using
capa as the given filename most likely uses the same encoding, but we
force UTF-8 in our tests. As we are planing to remove Python 2 support
is not wortwhile to invest time in making this test working. Instead,
test Python 2.7 only in Ubuntu.
2021-04-06 21:39:01 +02:00
Moritz Raabe
efe50d3313 ci: test on Windows and macOS
Run the tests on Windows and macOS to avoid failures OS related.

closes #460
2021-04-06 21:38:07 +02:00
Ana María Martínez Gómez
1062ba995e doc: add milestones link to release checklist
This makes it a bit easier to check if all milestoned issues/PRs are addressed, or reassign to a new milestone.

I am committing directly to master as this is a minor change which doesn't need review.
2021-04-06 10:21:43 +02:00
Ana María Martínez Gómez
7f93bd5b59 Merge pull request #512 from fireeye/williballenthin-patch-2
setup: bump viv to v1.0.1
2021-04-06 10:17:44 +02:00
Willi Ballenthin
275d170680 setup: bump viv to v1.0.1 2021-04-05 21:22:17 -06:00
Moritz
6d7e10b804 Merge pull request #511 from fireeye/ci/fix-typos
fix submodule typos
2021-04-05 13:13:41 +02:00
Moritz Raabe
25944864f7 fix submodule typos 2021-04-05 12:52:08 +02:00
Capa Bot
5e84a16eba Sync capa rules submodule 2021-04-01 16:44:59 +00:00
Capa Bot
244ec163a3 Sync capa-testfiles submodule 2021-04-01 16:44:11 +00:00
Capa Bot
dabd2174d4 Sync capa rules submodule 2021-03-29 16:25:18 +00:00
Moritz
f8d2b41a86 Merge pull request #495 from fireeye/gh/add-pr-template
add PR template
2021-03-29 17:31:05 +02:00
Capa Bot
902972a1ee Sync capa-testfiles submodule 2021-03-29 12:49:24 +00:00
Capa Bot
bddb5fbd2f Sync capa rules submodule 2021-03-26 11:17:46 +00:00
Capa Bot
adfd769963 Sync capa-testfiles submodule 2021-03-26 11:00:35 +00:00
Capa Bot
c75e70ec74 Sync capa-testfiles submodule 2021-03-26 11:00:15 +00:00
Moritz
6118183105 Merge pull request #504 from fireeye/mr-tz-patch-1
Update setup.py
2021-03-26 11:58:52 +01:00
Moritz
da755d8411 Update setup.py 2021-03-26 11:44:04 +01:00
mike-hunhoff
742e03d90f Merge pull request #503 from fireeye/explorer/update-readme
updating capa explorer README
2021-03-25 14:51:21 -06:00
Capa Bot
744228a03e Sync capa rules submodule 2021-03-25 20:48:41 +00:00
Michael Hunhoff
5d1c6f54cd updating capa explorer README 2021-03-25 14:30:28 -06:00
mike-hunhoff
0a3dd4600b Merge pull request #468 from fireeye/features/support-string-values-special-chars
add support for string features with special characters e.g. '\n'
2021-03-25 12:58:00 -06:00
Michael Hunhoff
0289891d07 merging upstream 2021-03-25 12:43:59 -06:00
Michael Hunhoff
87cdf837e6 merging upstream 2021-03-25 12:42:36 -06:00
Capa Bot
ea4c7d6403 Sync capa rules submodule 2021-03-25 18:37:22 +00:00
Capa Bot
2807549564 Sync capa rules submodule 2021-03-25 07:21:21 +00:00
Capa Bot
c0fe96cec6 Sync capa-testfiles submodule 2021-03-25 07:17:41 +00:00
mike-hunhoff
8c967ac237 Merge pull request #500 from fireeye/explorer/improve-rulegen-search
explorer: add checks to validate matched data when searching
2021-03-24 15:55:34 -06:00
Michael Hunhoff
c48b46e932 explorer: adding checks to validate matched data when searching 2021-03-24 15:33:20 -06:00
mike-hunhoff
49d1af7798 improve unit tests for strings containing special characters
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2021-03-24 13:22:18 -06:00
mike-hunhoff
d44fd008ae improve unit tests for strings containing special characters
Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
2021-03-24 13:22:04 -06:00
Moritz Raabe
c0c9ea3403 incorprate Ana's feedback 2021-03-24 09:22:40 +01:00
Michael Hunhoff
21359da766 updating test for strings with special characaters 2021-03-23 16:02:47 -06:00
Michael Hunhoff
e51c79c241 adding lint for incorrect rule string format, refined rendering for strings 2021-03-23 15:55:48 -06:00
Willi Ballenthin
e22113c20d ida: support 7.6
closes #496
2021-03-23 08:43:33 -06:00
Capa Bot
195bae903f Sync capa rules submodule 2021-03-23 12:25:20 +00:00
Moritz Raabe
5aff21a9a1 add PR template 2021-03-23 10:52:01 +01:00
Ana María Martínez Gómez
6f289d1b8e Merge pull request #476 from Ana06/tag-workflow 2021-03-23 09:54:59 +01:00
Moritz
71b21aec59 Merge pull request #492 from fireeye/ignore-gitfiles
rule loading: ignore files starting with .git
2021-03-23 08:16:29 +01:00
Capa Bot
42a87d4eaa Sync capa-testfiles submodule 2021-03-23 07:14:58 +00:00
Capa Bot
51d125642f Sync capa rules submodule 2021-03-23 07:14:21 +00:00
mike-hunhoff
ddebf2e1cb Merge pull request #493 from fireeye/enhance/472
rule generator: support subscope rules
2021-03-22 17:28:43 -06:00
Michael Hunhoff
7f3e8f1fb1 adding support to match subscope rules and auto insert child statements when creating a new basic block subscope 2021-03-22 17:12:13 -06:00
Ana María Martínez Gómez
ab7dbcd2e4 Merge pull request #491 from fireeye/williballenthin-patch-3 2021-03-22 19:16:49 +01:00
Ana Maria Martinez Gomez
7e5cbddf5d doc: document release process
Add a release checklist.

Closes https://github.com/fireeye/capa/issues/184
2021-03-22 19:14:02 +01:00
Moritz Raabe
44f517c20d rule loading: ignore files starting with .git 2021-03-22 18:11:29 +01:00
Michael Hunhoff
7bf8c6e3a1 merging upstream 2021-03-22 10:33:36 -06:00
Michael Hunhoff
31ea683335 merge upstream 2021-03-22 09:53:07 -06:00
Willi Ballenthin
29d8f1fd27 ci: tests: pin OS version 2021-03-22 09:51:20 -06:00
Willi Ballenthin
a6c472bb2a ci: publish: pin OS version 2021-03-22 09:50:47 -06:00
Willi Ballenthin
b880d419a3 ci: build: pin OS versions 2021-03-22 09:50:04 -06:00
Capa Bot
a2ff87af8a Sync capa rules submodule 2021-03-22 15:45:10 +00:00
Willi Ballenthin
5b9c577380 Merge pull request #489 from fireeye/dependabot/pip/viv-utils-0.6.0
Bump viv-utils from 0.5.0 to 0.6.0
2021-03-22 09:39:52 -06:00
Capa Bot
4775e124db Sync capa rules submodule 2021-03-22 09:02:35 +00:00
Moritz
c243158d7c Merge pull request #486 from fireeye/fix/eol-improvements
EOL improvements
2021-03-22 09:58:29 +01:00
Capa Bot
8afc3f46f6 Sync capa rules submodule 2021-03-22 08:41:21 +00:00
dependabot[bot]
8b5dc54397 Bump viv-utils from 0.5.0 to 0.6.0
Bumps [viv-utils](https://github.com/williballenthin/viv-utils) from 0.5.0 to 0.6.0.
- [Release notes](https://github.com/williballenthin/viv-utils/releases)
- [Commits](https://github.com/williballenthin/viv-utils/compare/v0.5.0...v0.6.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-22 06:20:47 +00:00
Capa Bot
1dbb34df9f Sync capa-testfiles submodule 2021-03-21 19:28:58 +00:00
mike-hunhoff
9383f0bc77 Merge pull request #474 from fireeye/explorer/fix-471
explorer: adding support for multi-line tab and SHIFT + Tab
2021-03-19 19:11:14 -06:00
Willi Ballenthin
900a03c172 setup: bump viv-utils version for better FLIRT matching 2021-03-19 11:15:15 -06:00
Moritz Raabe
13306b71e0 add file 2021-03-19 09:40:44 +01:00
Moritz Raabe
8719a23de4 dos2unix 2021-03-19 09:40:44 +01:00
Moritz Raabe
7e0b5236af better deal with CRLF/LF issues 2021-03-19 09:40:43 +01:00
Moritz Raabe
c7798b3254 ensure LF end of line 2021-03-19 09:40:43 +01:00
Willi Ballenthin
7d668550f5 Merge pull request #485 from fireeye/ci/ensure-lf-eol
ensure LF end of line
2021-03-18 14:41:13 -06:00
Capa Bot
c945eaf804 Sync capa rules submodule 2021-03-18 20:41:05 +00:00
Moritz Raabe
1bfe0e0874 ensure LF end of line 2021-03-18 20:15:23 +01:00
Capa Bot
153c6a7b01 Sync capa-testfiles submodule 2021-03-18 18:04:33 +00:00
Ana Maria Martinez Gomez
30a83fa382 doc: Fix broken link in README
Introduced in https://github.com/fireeye/capa/pull/478
2021-03-16 16:37:33 +01:00
Willi Ballenthin
c0bcefe0bf Merge pull request #479 from Ana06/viv-utils5
setup: bump viv-utils to 0.5.0
2021-03-16 07:02:43 -06:00
Ana Maria Martinez Gomez
5d16a77891 ci: tag capa-rules on release
Add GitHub Action to tag capa-rules when releasing capa. The used tag
name is the same as the one in capa.
2021-03-16 12:45:02 +01:00
Ana Maria Martinez Gomez
cd01a01894 setup: bump viv-utils to 0.5.0
In viv-utils `getWorkspace` raises `IncompatibleVivVersion` on Python 3
when `vw.loadWorkspace(viv_file)` raises `UnicodeDecodeError`.

Fixes https://github.com/fireeye/capa/issues/469

As we use the same version in py2 and py3, define the viv-utils
requirement once.
2021-03-16 10:51:50 +01:00
Willi Ballenthin
df36bb9f35 Merge pull request #478 from Ana06/badges
doc: Improve README badges
2021-03-15 14:42:57 -06:00
William Ballenthin
8a3f5e423b setup: bump viv-utils version 2021-03-15 13:39:44 -06:00
William Ballenthin
177605aaf8 flirt: enable only on py3, ignore otherwise 2021-03-15 13:38:29 -06:00
Ana María Martínez Gómez
030893e125 Merge pull request #475 from Ana06/incompatible-viv
changelog: document incompatibility of viv files
2021-03-15 17:30:17 +01:00
Ana Maria Martinez Gomez
b2ab8ab54c doc: Improve README badges
- Add a link to the `PyPI - Python Version` badge. Otherwise it opens
the image when clicking on it, which is inconsistent with the other
labels. I arrived too late to point this out in:
https://github.com/fireeye/capa/pull/477
- Add release badge with last release version. This may help users to
realize that a new version has been released.
- Add downloads badge.
- Order labels by color.

Closes https://github.com/fireeye/capa/issues/196
2021-03-15 16:47:15 +01:00
Willi Ballenthin
12eb1b96de Merge pull request #477 from fireeye/mr-tz-patch-1
Update README.md with Python version badge
2021-03-15 08:35:27 -06:00
Moritz
cff7d4bad4 Update README.md 2021-03-15 11:54:11 +01:00
Ana Maria Martinez Gomez
a31c616a21 changelog: document incompatibility of viv files
`.viv` files (generated by vivisect) are not compatible between Python 2
and Python 3. This causes capa to raise an `UnicodeDecodeError`
exception and should be documented better. I'll add this change to the
release notes after the review.

Related to https://github.com/fireeye/capa/issues/469
2021-03-15 10:26:32 +01:00
Michael Hunhoff
3d2b4dcc26 adding support for multi-line tab and SHIFT + Tab 2021-03-11 17:13:43 -07:00
Michael Hunhoff
c7d24ee290 adding support for string features with special characters e.g. '\n' 2021-03-10 13:56:54 -07:00
mike-hunhoff
06c958f081 Merge pull request #465 from fireeye/explorer/fix-463
explorer: improve settings modification
2021-03-10 11:30:23 -07:00
Michael Hunhoff
b8efe585d5 fix 463, improve settings UI 2021-03-09 14:56:44 -07:00
Willi Ballenthin
e7eb2152cc Merge pull request #464 from fireeye/explorer/fix-462
fix 462
2021-03-09 12:13:54 -07:00
Michael Hunhoff
e1a8641399 fixes 462, default to empty string when accessing rule path stored in ida_settings 2021-03-09 12:09:35 -07:00
Capa Bot
cffac62e68 Sync capa rules submodule 2021-03-09 10:00:48 +00:00
Ana María Martínez Gómez
7a8c0572e9 Merge pull request #455 from Ana06/v1-6-0 2021-03-09 10:48:01 +01:00
Ana Maria Martinez Gomez
5596d5f8b2 version: bump to v1.6.0 2021-03-09 10:36:26 +01:00
Ana Maria Martinez Gomez
06fd02cd61 changelog: v1.6.0
This release adds the capa explorer rule generator plugin for IDA Pro,
vivisect support for Python 3 and 12 new rules. We appreciate everyone
who opened issues, provided feedback, and contributed code and rules.
Thank you also to the vivisect development team (rakuy0, atlas0
fd00m) for the Python 3 support (v1.0.0) and the fixes for Python 2
(v0.2.1). This is the last capa release which supports Python 2. Next
release will be Python 3 only.
2021-03-09 10:36:26 +01:00
Capa Bot
6b9d1047cf Sync capa rules submodule 2021-03-08 19:39:47 +00:00
Ana Maria Martinez Gomez
a7b3fd72ca changelog: v1.5.1 2021-03-08 20:09:31 +01:00
Ana María Martínez Gómez
dd3deb2358 Merge pull request #454 from fireeye/mr-tz-patch-1
setup: bump viv to 0.2.1
2021-03-08 11:36:18 +01:00
Moritz
c99fce3183 setup: bump viv to 0.2.1 2021-03-08 09:07:04 +01:00
William Ballenthin
4db6227d84 ci: build: test exe: run in debug mode to see messages 2021-03-05 15:49:31 -07:00
William Ballenthin
30e1d409dd pyinstaller: package default signatures into standalone exe 2021-03-05 15:46:23 -07:00
William Ballenthin
ff8a6f1d57 main: use default signature set found in source directory 2021-03-05 15:45:56 -07:00
William Ballenthin
9b5d6f8df0 ci: enable test building of standalone exe in CI 2021-03-05 15:35:42 -07:00
William Ballenthin
1e8919c6e6 pep8 2021-03-05 15:27:44 -07:00
William Ballenthin
1ee7b7b856 merge master 2021-03-05 15:23:47 -07:00
Willi Ballenthin
3e55581bf7 Merge pull request #450 from fireeye/feature-refactor-args
refactor common cli argument handling
2021-03-05 15:07:50 -07:00
Willi Ballenthin
dfbe1418d4 Merge pull request #452 from fireeye/feature-py3-pyinstaller
pyinstaller: update for py3/pyinstaller 4.2
2021-03-05 15:06:47 -07:00
William Ballenthin
7671fca373 pep8 2021-03-05 13:27:16 -07:00
William Ballenthin
c01dde3fb2 ci: disable test building of pyinstaller upon push 2021-03-05 13:26:15 -07:00
William Ballenthin
bb17adeda2 pyinstaller: smda: collect capstone shared library 2021-03-05 13:23:15 -07:00
Willi Ballenthin
9f743f1c59 main: fix reference error 2021-03-05 13:19:54 -07:00
William Ballenthin
ee85c929da pyinstaller: install capstone for smda 2021-03-05 12:59:21 -07:00
William Ballenthin
6f9c660082 ci: test pyinstaller CI 2021-03-05 12:55:19 -07:00
William Ballenthin
e02bb7f5a1 pep8 2021-03-05 12:53:50 -07:00
William Ballenthin
9aaaa044da ci: use py3.9 and pyinstaller 4.2 to build standalone binaries 2021-03-05 12:52:38 -07:00
William Ballenthin
54da8444df pyinstaller: update for py3/pyinstaller 4.2
closes #451
2021-03-05 12:40:21 -07:00
William Ballenthin
063e1229bc pep8 2021-03-05 11:10:12 -07:00
William Ballenthin
eacd70329a merge from master, sorry 2021-03-05 11:06:40 -07:00
William Ballenthin
3a1d5d068c scripts: use common argument handler
closes #449
2021-03-05 10:58:40 -07:00
William Ballenthin
f2749d884f main: factor out common cli argument handling
ref #449
2021-03-05 10:57:39 -07:00
William Ballenthin
bdea61f93b scripts: remove old migration script 2021-03-05 10:57:14 -07:00
William Ballenthin
6006e87c5e pep8 2021-03-05 09:40:43 -07:00
William Ballenthin
1e8161b24e setup: bump viv-utils for FLIRT 2021-03-05 09:39:47 -07:00
William Ballenthin
a3e6d1b611 scripts: add helper to show function id matches 2021-03-05 08:38:02 -07:00
William Ballenthin
1a93999cc0 capa: main: factor loading of flirt signatures into its own routine 2021-03-05 08:34:33 -07:00
William Ballenthin
53684adbdd sigs: add license to test files 2021-03-04 18:07:34 -07:00
William Ballenthin
d3caecc551 pep8 2021-03-04 18:06:06 -07:00
William Ballenthin
004ddb3e66 main: load gzip compressed .pat files 2021-03-04 18:04:46 -07:00
William Ballenthin
20894124e6 tests: test FLIRT matching 2021-03-04 15:50:05 -07:00
William Ballenthin
22c4e3b8c2 viv: cleanup flirt changes 2021-03-04 15:46:14 -07:00
William Ballenthin
c2a4629c62 scripts: add cli arguments to specify signatures 2021-03-04 15:04:33 -07:00
William Ballenthin
c0f4fe6867 merge master 2021-03-04 14:59:17 -07:00
William Ballenthin
f2c95568bd main: add FLIRT signature matching configuration 2021-03-04 14:52:22 -07:00
William Ballenthin
358aab85e7 viv: move FLIRT matching into viv-utils 2021-03-04 14:51:40 -07:00
Ana María Martínez Gómez
829274cd5e Merge pull request #421 from Ana06/viv-py3 2021-03-03 21:40:08 +01:00
Ana Maria Martinez Gomez
c522f5094a Use -j option in test_backend_option
Use `-j` option in `test_backend_option` to check the extractor and that
rules have been extracted. This way we don't need to check if a concrete
rule matches, but only that at least a rule matches.
2021-03-03 18:33:20 +01:00
Ana Maria Martinez Gomez
29b6772721 Test backend option
As `get_extractor` returns only vivisect now, `test_main` is not run for
smda. Test that capa works with all backends. It doesn't test that the
backend is actually called.
2021-03-03 17:36:51 +01:00
Ana Maria Martinez Gomez
695b5b50ab Remove va not None check
Instead of checking if `va` is `None in `get_section()` we should avoid
calling this function with `None`. This have been fixed in the following
PR, so this is not longer needed:
https://github.com/fireeye/capa/pull/442
2021-03-03 17:36:51 +01:00
Ana Maria Martinez Gomez
42af7b2d8b Use default backend instead of None
Set the `backend` variable to the default backend by default instead to
`None`. The `backend` variable is needed in Python 2 as `args.backend`
is only set in Python 3. Although the value of the backend variable is
ignored in Python 2, so that the default value is not used.

Co-authored-by: William Ballenthin <william.ballenthin@fireeye.com>
2021-03-03 17:36:51 +01:00
Ana Maria Martinez Gomez
079a9b5204 Remove backend option from Python 2
Do only provide the backend option in Python 3, as there is only one
backend in Python 2. This way we keep the help text simpler.
2021-03-03 17:36:51 +01:00
Ana Maria Martinez Gomez
e5048fd3ac Add missing va parameter to SegmentationViolation
The `envi.SegmentationViolation()` was missing the `va` required
parameter. This has started failing now, because calling
`vw.getSegment(0x4BA190)` for the `tests/data/mimikatz.exe_` produces
different results in Python 2 and Python 3. It returns `None` in Python
3 while the output in Python 2 is:
`(4939776, 16840, '.data', 'mimikatz')`

I have reported the issue to vivisect:
https://github.com/vivisect/vivisect/issues/370
2021-03-03 17:36:51 +01:00
Ana Maria Martinez Gomez
18eaea95fa Fix TypeError exception in Python3
`va` can be None and this causes Python 3 to raise a TypeError
exception. This is caused by the following breaking change in Python3:
> The ordering comparison operators (<, <=, >=, >) raise a TypeError
> exception when the operands don’t have a meaningful natural ordering.

This didn't failed in the previously tried vivisect version (master from
one week ago and not the release). This may have been caused by a bug in
vivisect that has been fixed.
2021-03-03 17:36:51 +01:00
Ana Maria Martinez Gomez
a4a0a56448 Vivisect 1.0.0 released
Vivisect 1.0.0 (Python 3) has been released, so we do not need to link
to my GitHub branch anymore.

https://pypi.org/project/vivisect
2021-03-03 17:36:50 +01:00
Ana Maria Martinez Gomez
40ed2f39a4 Make backend a required parameter in get_extractor
Make the `backend` argument required in the `get_extractor` internal
routine. Specify a backend in the scripts which call this function. Add
a CLI backend option in capa/features/freeze.py as well.
2021-03-03 17:36:50 +01:00
Ana Maria Martinez Gomez
2859b037aa Use constants for backend option
Use constants instead of string literals for the backend option.
2021-03-03 17:36:50 +01:00
Ana Maria Martinez Gomez
bbb7878e0a Enable tests for vivisect in Python3
Now we support vivisect as backend in Python3. We should test it.
2021-03-03 17:36:50 +01:00
Ana Maria Martinez Gomez
fc438866ec Add option to select the backend in Py3
Now we have two working backends in Python3! Add an option to select
which one to use. With this code, vivisect is the default backend, but
this is really easy to change. We could do some analysis to see if smda
performances better than vivisect once the vivisect implementation.
2021-03-03 17:36:50 +01:00
Ana Maria Martinez Gomez
2da2f498a2 Add script to compare vivisect Python 2 vs 3
Compare the performance of vivisect Python 2 vs 3 by counting the number
of feature of each type extracted for every binary in `tests/data`.
Render the ones that perform bad (under a threshold - 98) and the total
performance. Render also the running time per binary for both Python 2 and 3.

From this result, it seems that vivisect behaves properly with Python3.
2021-03-03 17:36:50 +01:00
Ana Maria Martinez Gomez
29dffffe1b Python3 support for vivisect
Vivisect has moved to Python3. Allow to run vivisect with Python3 in
capa.

I am using the following version of vivisect (which includes fixes for
some bugs I have found and some open PRs in vivisect):
https://github.com/Ana06/vivisect/tree/py-3
2021-03-03 17:36:49 +01:00
Capa Bot
1ecaad5413 Sync capa rules submodule 2021-03-02 15:06:24 +00:00
Willi Ballenthin
cd56d672c0 Merge pull request #442 from fireeye/williballenthin-patch-2
viv: ignore empty branch targets
2021-03-01 08:43:26 -07:00
Willi Ballenthin
68aed3c190 insn: better document when branch va may be none 2021-02-28 23:03:08 -07:00
William Ballenthin
f16ecd837e viv: flirt: add more documentation 2021-02-26 05:02:10 -07:00
Willi Ballenthin
68fcc03d5c viv: ignore empty branch targets
but what does this really mean? why would `getBranches` return `None`?

closes #441
2021-02-25 13:34:59 -07:00
William Ballenthin
bfcae0e754 extractors: viv: match flirt signatures [wip] 2021-02-25 12:21:27 -07:00
William Ballenthin
1b2c8880ee capa: extractors: sketch API extension to support function id 2021-02-25 12:20:29 -07:00
Ana Maria Martinez Gomez
fa7d58d01a Add missing va parameter to SegmentationViolation
The `envi.SegmentationViolation()` was missing the `va` required
parameter. This has started failing now, because calling
`vw.getSegment(0x4BA190)` for the `tests/data/mimikatz.exe_` produces
different results in Python 2 and Python 3. It returns `None` in Python
3 while the output in Python 2 is:
`(4939776, 16840, '.data', 'mimikatz')`

I have reported the issue to vivisect:
https://github.com/vivisect/vivisect/issues/370
2021-02-25 11:20:45 +01:00
Ana Maria Martinez Gomez
ec558f377a Fix TypeError exception in Python3
`va` can be None and this causes Python 3 to raise a TypeError
exception. This is caused by the following breaking change in Python3:
> The ordering comparison operators (<, <=, >=, >) raise a TypeError
> exception when the operands don’t have a meaningful natural ordering.

This didn't failed in the previously tried vivisect version (master from
one week ago and not the release). This may have been caused by a bug in
vivisect that has been fixed.
2021-02-25 10:15:49 +01:00
Ana Maria Martinez Gomez
186eba7197 Vivisect 1.0.0 released
Vivisect 1.0.0 (Python 3) has been released, so we do not need to link
to my GitHub branch anymore.

https://pypi.org/project/vivisect
2021-02-25 10:05:04 +01:00
Ana Maria Martinez Gomez
d28ba3c628 Make backend a required parameter in get_extractor
Make the `backend` argument required in the `get_extractor` internal
routine. Specify a backend in the scripts which call this function. Add
a CLI backend option in capa/features/freeze.py as well.
2021-02-25 10:04:19 +01:00
Ana Maria Martinez Gomez
a026cb84d1 Use constants for backend option
Use constants instead of string literals for the backend option.
2021-02-25 09:35:40 +01:00
Ana Maria Martinez Gomez
3acc3eeabd Enable tests for vivisect in Python3
Now we support vivisect as backend in Python3. We should test it.
2021-02-25 09:35:40 +01:00
Ana Maria Martinez Gomez
a92d2af7f8 Add option to select the backend in Py3
Now we have two working backends in Python3! Add an option to select
which one to use. With this code, vivisect is the default backend, but
this is really easy to change. We could do some analysis to see if smda
performances better than vivisect once the vivisect implementation.
2021-02-25 09:35:40 +01:00
Ana Maria Martinez Gomez
adcb683458 Add script to compare vivisect Python 2 vs 3
Compare the performance of vivisect Python 2 vs 3 by counting the number
of feature of each type extracted for every binary in `tests/data`.
Render the ones that perform bad (under a threshold - 98) and the total
performance. Render also the running time per binary for both Python 2 and 3.

From this result, it seems that vivisect behaves properly with Python3.
2021-02-25 09:35:40 +01:00
Capa Bot
939b29bf60 Sync capa rules submodule 2021-02-24 23:00:34 +00:00
Ana Maria Martinez Gomez
e4925613b3 Python3 support for vivisect
Vivisect has moved to Python3. Allow to run vivisect with Python3 in
capa.

I am using the following version of vivisect (which includes fixes for
some bugs I have found and some open PRs in vivisect):
https://github.com/Ana06/vivisect/tree/py-3
2021-02-24 17:55:39 +01:00
Capa Bot
2f6a6e4628 Sync capa rules submodule 2021-02-24 08:07:52 +00:00
Capa Bot
7938ea34d0 Sync capa rules submodule 2021-02-24 08:06:30 +00:00
Capa Bot
ed94e36f7a Sync capa rules submodule 2021-02-24 00:12:19 +00:00
mike-hunhoff
1c3a8df136 Merge pull request #439 from fireeye/explorer/rulegen-support-file-scope
adding file scope support to rule generator IDA plugin
2021-02-23 11:50:54 -07:00
Michael Hunhoff
9f254b22ee adding file scope support to rule generator IDA plugin 2021-02-23 11:10:34 -07:00
Capa Bot
753f8ce84e Sync capa rules submodule 2021-02-23 17:33:38 +00:00
Capa Bot
acf3b549de Sync capa rules submodule 2021-02-23 15:29:20 +00:00
Capa Bot
669f6dcf98 Sync capa rules submodule 2021-02-23 15:23:19 +00:00
Capa Bot
e4f7c4aab1 Sync capa rules submodule 2021-02-23 15:22:43 +00:00
Moritz
5836d55e21 Merge pull request #438 from fireeye/explorer/show-results-by-function
explorer: adding option to show results by function
2021-02-22 18:23:44 +01:00
Michael Hunhoff
e17bf1a1f4 explorer: adding option to show results by function 2021-02-22 08:16:18 -07:00
Willi Ballenthin
acb253ae9c Merge pull request #437 from fireeye/scripts/show-capabilities
update to support running in IDA w/ Python 3
2021-02-19 17:02:53 -07:00
Michael Hunhoff
cc0aaa301f update to support running in IDA w/ Python 3 2021-02-19 14:28:20 -07:00
mike-hunhoff
4256316045 Merge pull request #436 from fireeye/fix/ida/unmapped-data-ref
check for unmapped addresses when resolving data references
2021-02-19 12:58:16 -07:00
Capa Bot
78ab0c9400 Sync capa-testfiles submodule 2021-02-19 19:39:18 +00:00
Capa Bot
944a670af0 Sync capa rules submodule 2021-02-19 17:17:33 +00:00
Michael Hunhoff
e4e517b334 checked for unmapped address when resolving data references 2021-02-19 10:07:23 -07:00
Capa Bot
ccd7f1ee4b Sync capa-testfiles submodule 2021-02-19 09:54:02 +00:00
Capa Bot
9db7ed88aa Sync capa rules submodule 2021-02-18 21:36:08 +00:00
Capa Bot
a5e7497f56 Sync capa-testfiles submodule 2021-02-18 21:35:02 +00:00
Capa Bot
754f302493 Sync capa rules submodule 2021-02-18 17:56:06 +00:00
Moritz
7783543153 Merge pull request #429 from fireeye/scripts/multiple-backends-show-features
mirror show-capabilities-by-function to enable multiple backends
2021-02-18 09:33:36 +01:00
Moritz
b02f92b3ea Merge pull request #428 from fireeye/linter/ntoskrnl-ntdll-overlap
linter: adding ntoskrnl, ntdll overlap lint
2021-02-18 09:23:02 +01:00
Michael Hunhoff
47b3ef29be removing viv dep from show-capabilities-by-function.py 2021-02-17 14:49:52 -07:00
Michael Hunhoff
1eb615f97c mirror show-capabilities-by-function to enable multiple backends 2021-02-17 14:40:33 -07:00
mike-hunhoff
cfa904a0a0 Merge pull request #426 from fireeye/explorer/rule-generator
initial commit of capa explorer rule generator plugin for IDA Pro
2021-02-17 13:44:54 -07:00
Michael Hunhoff
2d34458d10 linter: adding ntoskrnl, ntdll overlap lint 2021-02-17 13:29:36 -07:00
Capa Bot
e39713c4fd Sync capa rules submodule 2021-02-17 17:10:12 +00:00
Capa Bot
320b734da8 Sync capa rules submodule 2021-02-17 17:00:43 +00:00
Capa Bot
887848625c Sync capa-testfiles submodule 2021-02-17 16:52:43 +00:00
Capa Bot
685f06582d Sync capa rules submodule 2021-02-17 15:18:16 +00:00
Capa Bot
a3c21dba32 Sync capa rules submodule 2021-02-17 14:59:46 +00:00
Capa Bot
9744cde8aa Sync capa rules submodule 2021-02-17 07:27:24 +00:00
Capa Bot
0ba8c9ec00 Sync capa-testfiles submodule 2021-02-16 23:44:50 +00:00
Capa Bot
0764c603b4 Sync capa-testfiles submodule 2021-02-16 23:32:23 +00:00
mike-hunhoff
2d4f7a6946 Update README.md 2021-02-12 14:38:11 -07:00
mike-hunhoff
5346eec84d Update README.md 2021-02-12 14:35:34 -07:00
Michael Hunhoff
b704dd967b updating README related to capa explorer 2021-02-12 14:32:08 -07:00
Michael Hunhoff
84ace24b35 merging upstream 2021-02-12 14:19:23 -07:00
Michael Hunhoff
ea42f76cff updating README related to capa explorer 2021-02-12 14:18:30 -07:00
Michael Hunhoff
dd147dd040 format fixes, strip strings before display 2021-02-12 12:03:48 -07:00
Capa Bot
9a79136d15 Sync capa-testfiles submodule 2021-02-11 15:19:46 +00:00
Capa Bot
b722dd016a Sync capa rules submodule 2021-02-11 07:39:06 +00:00
Capa Bot
054853dc06 Sync capa-testfiles submodule 2021-02-11 07:36:27 +00:00
Capa Bot
e5ceef52c6 Sync capa rules submodule 2021-02-10 16:11:34 +00:00
Capa Bot
92747e8efc Sync capa-testfiles submodule 2021-02-10 14:11:34 +00:00
Capa Bot
6171de54f9 Sync capa-testfiles submodule 2021-02-10 14:05:17 +00:00
Capa Bot
287ef31081 Sync capa rules submodule 2021-02-10 13:44:47 +00:00
Willi Ballenthin
8121f291c3 version: bump to v1.5.1 2021-02-09 09:20:03 -07:00
Moritz
b721b5fcff Merge pull request #420 from fireeye/williballenthin-patch-2
setup: pin viv-utils version
2021-02-09 16:49:11 +01:00
Willi Ballenthin
521dfe0337 setup: bump viv-utils to 0.3.19 2021-02-09 08:18:17 -07:00
Capa Bot
7dc78b7837 Sync capa rules submodule 2021-02-09 15:17:09 +00:00
Michael Hunhoff
1a804ed97b merge upstream 2021-02-09 07:55:53 -07:00
Capa Bot
6636b9d56c Sync capa-testfiles submodule 2021-02-09 12:56:48 +00:00
Capa Bot
325c6cc805 Sync capa rules submodule 2021-02-09 09:58:41 +00:00
Capa Bot
6a6e205973 Sync capa-testfiles submodule 2021-02-08 19:07:40 +00:00
Capa Bot
46ec25d286 Sync capa rules submodule 2021-02-08 17:49:32 +00:00
Capa Bot
6e33a22676 Sync capa rules submodule 2021-02-08 17:48:52 +00:00
Capa Bot
6e81de9e44 Sync capa rules submodule 2021-02-08 17:45:01 +00:00
Willi Ballenthin
03f7bbc3a5 setup: pin viv-utils version 2021-02-08 10:30:31 -07:00
Willi Ballenthin
4354bc9108 Merge pull request #415 from fireeye/williballenthin-patch-2
v1.5.0
2021-02-08 09:55:43 -07:00
Willi Ballenthin
b8fcc2ff0c Merge pull request #417 from fireeye/smda/calls-from-no-api
remove apirefs from calls from
2021-02-08 09:54:04 -07:00
Moritz Raabe
55b7ae10a7 remove apirefs from calls from
closes #416
2021-02-08 11:56:01 +01:00
Willi Ballenthin
6d2a6c98d1 changelog: v1.5.0 2021-02-05 10:59:30 -07:00
Capa Bot
05998b5d05 Sync capa-testfiles submodule 2021-02-04 08:19:32 +00:00
Capa Bot
1063f3fcda Sync capa rules submodule 2021-02-03 18:13:29 +00:00
Capa Bot
93c5e4637b Sync capa rules submodule 2021-02-03 15:15:51 +00:00
Moritz
073c2b5754 Merge pull request #412 from fireeye/ida/meta-add-baseaddr
add imagebase to IDA meta data
2021-02-02 16:48:22 +01:00
mike-hunhoff
ef41d74b82 Merge pull request #411 from fireeye/fix/410
fixes #410
2021-02-02 08:38:23 -07:00
Moritz Raabe
84b3f38810 add imagebase to IDA meta data 2021-02-02 13:54:46 +01:00
mike-hunhoff
2288f38a11 Update capa/main.py
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2021-02-01 12:45:36 -07:00
mike-hunhoff
dbc4e06657 Update capa/main.py
Co-authored-by: Willi Ballenthin <willi.ballenthin@gmail.com>
2021-02-01 12:45:29 -07:00
Michael Hunhoff
2433777a76 fixes #410 2021-02-01 11:43:24 -07:00
Moritz
bb7001f5f2 Merge pull request #409 from fireeye/fix/extract-bytes
improve bytes feature extraction
2021-02-01 17:38:40 +01:00
Moritz Raabe
9b5aaa40de improve bytes feature extraction 2021-02-01 17:17:22 +01:00
Capa Bot
96d74f48f4 Sync capa rules submodule 2021-02-01 11:55:33 +00:00
Michael Hunhoff
c8a99c247c rulegen python2.x support 2021-01-29 12:45:04 -07:00
Michael Hunhoff
9f50a37e40 rulegen filtering basic blocks, adding support for double-click to add feature 2021-01-29 11:47:58 -07:00
Michael Hunhoff
54c9e39654 rulegen reorder context menu actions 2021-01-29 11:11:41 -07:00
Michael Hunhoff
3386a1e9f9 rulegen adding vert and hort splitters, moving save button to right 2021-01-29 10:51:26 -07:00
Michael Hunhoff
b413f2eafe rulegen adding support for sync between editor and preview windows 2021-01-28 17:15:18 -07:00
Capa Bot
f07af25a6a Sync capa rules submodule 2021-01-28 16:52:21 +00:00
Willi Ballenthin
14e65c4601 Merge pull request #401 from fireeye/linter-format
Lint rule formatting and improved rule dump
2021-01-28 09:18:20 -07:00
Capa Bot
b5c2fb0259 Sync capa rules submodule 2021-01-28 16:06:09 +00:00
Capa Bot
92d98db7bb Sync capa-testfiles submodule 2021-01-28 15:25:17 +00:00
Michael Hunhoff
9caafedb8d merging upstream 2021-01-28 08:14:16 -07:00
Moritz
e6f7ef604a Merge pull request #404 from fireeye/bugfix/403
fixing #403
2021-01-28 11:17:39 +01:00
Moritz Raabe
0eb8d3e47c fix time debug output 2021-01-28 11:09:25 +01:00
Moritz Raabe
072e30498b adjust negative hex numbers in to_yaml 2021-01-28 10:54:17 +01:00
Moritz Raabe
d6e73577af dont change quotes when dumping 2021-01-28 10:54:17 +01:00
Moritz Raabe
a81f98be8e manual adjust negative numbers 2021-01-28 10:54:17 +01:00
Moritz Raabe
0980e35c29 simplify string comparison 2021-01-28 10:54:17 +01:00
Moritz Raabe
336c2a3aff add option to only check reformat status 2021-01-28 10:54:17 +01:00
Moritz Raabe
e3055bc740 check rule format consistency 2021-01-28 10:54:17 +01:00
Capa Bot
9406e3dbfb Sync capa rules submodule 2021-01-28 09:52:43 +00:00
Moritz
5307b7e1b1 Merge pull request #408 from fireeye/fix/lint-lib-path
adjust expected lib path and log time
2021-01-28 10:28:30 +01:00
Moritz Raabe
f18a8f5b31 adjust expected lib path and log time 2021-01-28 10:18:03 +01:00
Moritz
cfe99c4b72 Merge pull request #407 from fireeye/fix/lint-logging
disable extractor progress
2021-01-28 09:25:07 +01:00
Moritz Raabe
0d439c0f55 disable extractor progress 2021-01-28 09:22:15 +01:00
Moritz
6288a96a8b Merge pull request #406 from fireeye/ci/disable-python36
Disable Python 3.6 tests
2021-01-28 08:35:42 +01:00
Moritz
819b6f6ccf Merge pull request #402 from fireeye/lib-rules-subscoped
potential fix for #398
2021-01-28 08:35:28 +01:00
Moritz Raabe
4bc06aa8cd closes #405 2021-01-28 08:23:15 +01:00
Moritz Raabe
7b64425c24 update doc and test case 2021-01-28 08:18:23 +01:00
Michael Hunhoff
44c9d6a22b fixing #403 2021-01-27 18:29:53 -07:00
Moritz Raabe
c750447d62 potential fix for #398 2021-01-27 17:59:56 +01:00
Michael Hunhoff
b1c99d82fd rulegen adding special handling for count description 2021-01-22 09:41:17 -07:00
Michael Hunhoff
10db79f636 rulegen changes for backwards compat w/ Python 2.x 2021-01-22 08:22:37 -07:00
Willi Ballenthin
059ec8f3f2 Merge pull request #400 from fireeye/ci/enable-py39-2
bump smda, enable Python 3.9
2021-01-22 07:18:54 -07:00
Moritz Raabe
2c5508febd bump smda, enable Python 3.9 2021-01-22 10:00:25 +01:00
Capa Bot
905fff041b Sync capa rules submodule 2021-01-21 21:32:42 +00:00
Michael Hunhoff
cd27a64f4e rulegen clear ruleset cache when user configures new directory 2021-01-21 14:15:52 -07:00
Michael Hunhoff
d1b7a5c2e4 rulegen fixing bug in handling of subscope-rules 2021-01-21 14:05:24 -07:00
Michael Hunhoff
4b81b086db rulegen removing uneeded file 2021-01-21 10:19:37 -07:00
Michael Hunhoff
0db42c28a7 rulegen adding support to use cached ruleset, user click reset to reload rules from disk 2021-01-21 10:09:43 -07:00
Michael Hunhoff
0eca6ce2e3 rulegen adding save button, reducing menu complexity 2021-01-21 09:29:10 -07:00
Michael Hunhoff
34685bf80e rulegen adding header comment to generated rules 2021-01-20 15:22:56 -07:00
Michael Hunhoff
271dc2a6a9 rulegen add ability to configure default values for rule author and scope 2021-01-20 15:12:44 -07:00
Michael Hunhoff
bf0376f73f rulegen adding auto check if new rule matches current function 2021-01-20 14:31:48 -07:00
Michael Hunhoff
cf8656eb2d adding search bar for feature tree in rule generator 2021-01-19 12:03:15 -07:00
Willi Ballenthin
20ce29b033 Merge pull request #396 from fireeye/dependabot/pip/smda-1.5.11
Bump smda from 1.5.10 to 1.5.11
2021-01-19 08:21:00 -07:00
Capa Bot
4bd93a680e Sync capa-testfiles submodule 2021-01-18 08:02:29 +00:00
dependabot[bot]
c9bf7f424d Bump smda from 1.5.10 to 1.5.11
Bumps [smda](https://github.com/danielplohmann/smda) from 1.5.10 to 1.5.11.
- [Release notes](https://github.com/danielplohmann/smda/releases)
- [Commits](https://github.com/danielplohmann/smda/commits)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-18 06:44:33 +00:00
Capa Bot
4cde2e1a78 Sync capa rules submodule 2021-01-16 15:39:09 +00:00
Michael Hunhoff
15625b5f8c capa explorer rulegen -> adding styling; adding support for descriptions 2021-01-15 12:52:52 -07:00
Michael Hunhoff
e5f9da1f2b adding submenus to rulegen editor; empty expressions auto pruned from rulegen editor 2021-01-14 16:22:56 -07:00
Michael Hunhoff
ab33c46c87 init commit capa explorer rulegen 2021-01-14 15:46:24 -07:00
Capa Bot
48c045d381 Sync capa rules submodule 2021-01-12 18:30:44 +00:00
Capa Bot
2b385ead7f Sync capa rules submodule 2021-01-12 18:30:11 +00:00
Capa Bot
0fcc9f3df6 Sync capa-testfiles submodule 2021-01-12 18:27:32 +00:00
Capa Bot
b251202804 Sync capa-testfiles submodule 2021-01-12 18:27:11 +00:00
Capa Bot
6967010281 Sync capa-testfiles submodule 2021-01-12 18:26:12 +00:00
Capa Bot
7e0846e66a Sync capa rules submodule 2021-01-12 17:55:13 +00:00
Moritz
4e3daad96d Merge pull request #391 from fireeye/fix/freeze-base-addr
add base address to freeze
2021-01-11 11:30:29 +01:00
Capa Bot
37fb3da5db Sync capa rules submodule 2021-01-08 16:36:36 +00:00
Capa Bot
762f48957c Sync capa rules submodule 2021-01-08 15:16:32 +00:00
Capa Bot
c1af7b8783 Sync capa-testfiles submodule 2021-01-08 15:14:26 +00:00
Moritz Raabe
f89084677d add base address to freeze 2021-01-08 14:48:26 +01:00
Capa Bot
0716084bbb Sync capa-testfiles submodule 2021-01-08 08:46:53 +00:00
Capa Bot
a6c946e6c9 Sync capa rules submodule 2021-01-07 13:59:20 +00:00
Capa Bot
3f6e088faa Sync capa-testfiles submodule 2021-01-07 11:53:24 +00:00
Capa Bot
9abdd5813b Sync capa rules submodule 2021-01-07 07:47:28 +00:00
Capa Bot
f33ea36e6f Sync capa rules submodule 2021-01-05 15:49:04 +00:00
Moritz
8788e0a9c9 Merge pull request #388 from fireeye/ci/linter-update
lint with tags
2021-01-05 16:37:21 +01:00
Moritz Raabe
b1c1cb4b9b lint with --tag 2021-01-05 16:16:35 +01:00
Capa Bot
982d4ac472 Sync capa-testfiles submodule 2021-01-04 14:42:43 +00:00
Capa Bot
b7a8d667b9 Sync capa rules submodule 2021-01-04 12:51:43 +00:00
Capa Bot
8f8729df05 Sync capa-testfiles submodule 2020-12-30 19:06:28 +00:00
Capa Bot
e928d281dd Sync capa-testfiles submodule 2020-12-30 15:21:36 +00:00
Capa Bot
625583f5ab Sync capa rules submodule 2020-12-23 12:44:25 +00:00
Capa Bot
ab54553dd2 Sync capa rules submodule 2020-12-22 17:16:54 +00:00
Moritz
47bf7b1325 Merge pull request #375 from doomedraven/return_dict
add render to dict, is the same as default but just in dictionary so …
2020-12-22 15:52:50 +01:00
Moritz
145d75f579 Merge pull request #381 from fireeye/fix/viv-set-logger-levels
set level of more viv loggers explicitly
2020-12-22 15:52:05 +01:00
Capa Bot
01d976d7f7 Sync capa rules submodule 2020-12-22 13:17:37 +00:00
Capa Bot
095e3720ab Sync capa-testfiles submodule 2020-12-22 12:00:35 +00:00
Capa Bot
d62a37fe1f Sync capa-testfiles submodule 2020-12-21 16:17:33 +00:00
Capa Bot
5323f2fc31 Sync capa rules submodule 2020-12-17 17:14:43 +00:00
Capa Bot
5539cb0d08 Sync capa rules submodule 2020-12-17 17:12:21 +00:00
Capa Bot
76e80106d6 Sync capa-testfiles submodule 2020-12-17 09:29:56 +00:00
Capa Bot
9ab7b9a033 Sync capa rules submodule 2020-12-16 20:47:34 +00:00
Capa Bot
fe97d6a349 Sync capa-testfiles submodule 2020-12-15 19:23:15 +00:00
Capa Bot
2242c2afe8 Sync capa-testfiles submodule 2020-12-15 19:19:09 +00:00
Willi Ballenthin
ec25fb5c36 Merge pull request #384 from fireeye/dependabot/pip/smda-1.5.10
Bump smda from 1.5.9 to 1.5.10
2020-12-14 10:32:31 -07:00
dependabot[bot]
ce25f5cadd Bump smda from 1.5.9 to 1.5.10
Bumps [smda](https://github.com/danielplohmann/smda) from 1.5.9 to 1.5.10.
- [Release notes](https://github.com/danielplohmann/smda/releases)
- [Commits](https://github.com/danielplohmann/smda/commits)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-14 07:15:58 +00:00
Capa Bot
1099f40f19 Sync capa rules submodule 2020-12-12 05:43:31 +00:00
Capa Bot
70368b3f1e Sync capa rules submodule 2020-12-11 10:42:16 +00:00
Capa Bot
0181ebad45 Sync capa-testfiles submodule 2020-12-10 17:38:00 +00:00
DoomedRaven
e158e3f13c remove type hint to make CI happy 2020-12-08 21:46:39 +01:00
DoomedRaven
b1bbded23c black -l 120 . 2020-12-08 21:39:50 +01:00
DoomedRaven
b77d9d3738 isort --profile black --length-sort --line-width 120 capa_as_library.py 2020-12-08 21:34:42 +01:00
DoomedRaven
d0b2421752 isort capa_as_library.py 2020-12-08 20:53:26 +01:00
DoomedRaven
96b65a7c60 add example how to render it as library
```
>>> from capa_as_library import capa_details
>>> details = capa_details("/opt/CAPEv2/storage/analyses/83/binary", "dictionary")
>>> from pprint import pprint as pp
>>> pp(details)
{'ATTCK': {'DEFENSE EVASION': ['Obfuscated Files or Information [T1027]',
                               'Virtualization/Sandbox Evasion::System Checks '
                               '[T1497.001]'],
           'EXECUTION': ['Shared Modules [T1129]']},
 'CAPABILITY': {'anti-analysis/anti-vm/vm-detection': ['execute anti-VM '
                                                       'instructions (3 '
                                                       'matches)'],
                'anti-analysis/obfuscation/string/stackstring': ['contain '
                                                                 'obfuscated '
                                                                 'stackstrings'],
                'data-manipulation/encryption/rc4': ['encrypt data using RC4 '
                                                     'PRGA'],
                'executable/pe/section/rsrc': ['contain a resource (.rsrc) '
                                               'section'],
                'host-interaction/cli': ['accept command line arguments'],
                'host-interaction/environment-variable': ['query environment '
                                                          'variable'],
                'host-interaction/file-system/read': ['read .ini file',
                                                      'read file'],
                'host-interaction/file-system/write': ['write file (3 '
                                                       'matches)'],
                'host-interaction/process': ['get thread local storage value '
                                             '(3 matches)',
                                             'set thread local storage value '
                                             '(2 matches)'],
                'host-interaction/process/terminate': ['terminate process (3 '
                                                       'matches)'],
                'host-interaction/thread/terminate': ['terminate thread'],
                'linking/runtime-linking': ['link function at runtime (7 '
                                            'matches)',
                                            'link many functions at runtime'],
                'load-code/pe': ['parse PE header (3 matches)']},
 'MBC': {'ANTI-BEHAVIORAL ANALYSIS': ['Virtual Machine Detection::Instruction '
                                      'Testing [B0009.029]'],
         'ANTI-STATIC ANALYSIS': ['Disassembler Evasion::Argument Obfuscation '
                                  '[B0012.001]'],
         'CRYPTOGRAPHY': ['Encrypt Data::RC4 [C0027.009]',
                          'Generate Pseudo-random Sequence::RC4 PRGA '
                          '[C0021.004]']},
 'md5': 'ad56c384476a81faef9aebd60b2f4623',
 'path': '/opt/CAPEv2/storage/analyses/83/binary',
 'sha1': 'aa027d89f5d3f991ad3e14ffb681616a77621836',
 'sha256': '16995e059eb47de0b58a95ce2c3d863d964a7a16064d4298cee9db1de266e68d'}
>>>
```
2020-12-08 20:00:24 +01:00
Willi Ballenthin
177c90093e Merge pull request #380 from doomedraven/patch-1
fix is_ordinal IndexError
2020-12-08 09:21:53 -07:00
Moritz Raabe
28ee091107 set level of more viv loggers explicitly 2020-12-08 16:30:23 +01:00
doomedraven
64c71d8e6d fix is_ordinal IndexError
```
 Traceback (most recent call last):
   File "/opt/CAPE/utils/../lib/cuckoo/common/cape_utils.py", line 223, in flare_capa_details
     capabilities, counts = capa.main.find_capabilities(rules, extractor, disable_progress=True)
   File "/usr/local/lib/python2.7/dist-packages/capa/main.py", line 116, in find_capabilities
     function_matches, bb_matches, feature_count = find_function_capabilities(ruleset, extractor, f)
   File "/usr/local/lib/python2.7/dist-packages/capa/main.py", line 68, in find_function_capabilities
     for feature, va in extractor.extract_insn_features(f, bb, insn):
   File "/usr/local/lib/python2.7/dist-packages/capa/features/extractors/viv/__init__.py", line 84, in extract_insn_features
     for feature, va in capa.features.extractors.viv.insn.extract_features(f, bb, insn):
   File "/usr/local/lib/python2.7/dist-packages/capa/features/extractors/viv/insn.py", line 599, in extract_features
     for feature, va in insn_handler(f, bb, insn):
   File "/usr/local/lib/python2.7/dist-packages/capa/features/extractors/viv/insn.py", line 93, in extract_insn_api_features
     for name in capa.features.extractors.helpers.generate_symbols(dll, symbol):
   File "/usr/local/lib/python2.7/dist-packages/capa/features/extractors/helpers.py", line 61, in generate_symbols
     if not is_ordinal(symbol):
   File "/usr/local/lib/python2.7/dist-packages/capa/features/extractors/helpers.py", line 45, in is_ordinal
     return symbol[0] == "#"
 IndexError: string index out of range
```
2020-12-08 09:50:00 +01:00
Moritz
9ce0c94e17 Merge pull request #379 from fireeye/fix/nzxor-xor-instructions
add more xor instructions
2020-12-08 09:37:35 +01:00
Moritz Raabe
08c3372635 add more xor instructions 2020-12-08 09:21:50 +01:00
Capa Bot
2fafc70b69 Sync capa-testfiles submodule 2020-12-07 18:06:53 +00:00
Capa Bot
0e62ebe3a2 Sync capa-testfiles submodule 2020-12-07 17:16:01 +00:00
Moritz
1cc4d20b89 Merge pull request #373 from fireeye/ci/setup-dependabot
add dependabot config
2020-12-07 18:03:57 +01:00
Capa Bot
af4889894a Sync capa rules submodule 2020-12-04 08:31:42 +00:00
Moritz
429a5e1ea3 Merge pull request #378 from fireeye/fix/viv-string-extractor
fix: add viv extract strings for i386ImmMemOper operands
2020-12-04 08:55:23 +01:00
Moritz Raabe
4ef860eb07 fix: add viv extract strings for i386ImmMemOper operands 2020-12-03 20:24:29 +01:00
Capa Bot
b59ebf30c6 Sync capa-testfiles submodule 2020-12-03 18:57:45 +00:00
Capa Bot
a1ae8d54a6 Sync capa rules submodule 2020-12-02 15:24:15 +00:00
Capa Bot
8155207bea Sync capa rules submodule 2020-12-02 15:13:30 +00:00
Capa Bot
337d2cfa6d Sync capa rules submodule 2020-12-02 15:12:27 +00:00
Capa Bot
df2229782b Sync capa rules submodule 2020-12-02 15:08:55 +00:00
doomedraven
5920552649 small improvements 2020-12-01 20:31:56 +01:00
doomedraven
b4827fcb00 add render to dict, is the same as default but just in dictionary so simplifies the integrations 2020-12-01 19:43:54 +01:00
Willi Ballenthin
63983ccb65 Merge pull request #372 from doomedraven/patch-1
Simple example how to use capa as library
2020-12-01 06:56:44 -07:00
Willi Ballenthin
eac7e2b749 capa_as_library: style and comments 2020-12-01 06:54:55 -07:00
Moritz Raabe
65a365bca1 update halo requirements py2/3 2020-12-01 11:46:53 +01:00
Moritz Raabe
fecd0e11eb add dependabot config 2020-12-01 11:46:14 +01:00
doomedraven
51ad526cfc Simple example how to use capa as library
Just quick example how to use capa as library, to save time to someone, reading code and scripts
2020-12-01 11:20:49 +01:00
Moritz
10a062017d Merge pull request #370 from fireeye/pin-smda
pin smda
2020-12-01 11:10:23 +01:00
Moritz Raabe
0d351794db pin smda
addresses #369
2020-12-01 11:02:36 +01:00
Capa Bot
067e3ffced Sync capa-testfiles submodule 2020-11-30 19:36:59 +00:00
Capa Bot
50d55fae56 Sync capa-testfiles submodule 2020-11-23 17:55:56 +00:00
Capa Bot
ce63628d3d Sync capa rules submodule 2020-11-19 15:43:59 +00:00
Capa Bot
13df7f90f6 Sync capa rules submodule 2020-11-19 15:09:24 +00:00
Capa Bot
f5099b873d Sync capa rules submodule 2020-11-19 11:40:38 +00:00
Capa Bot
70eb38895d Sync capa-testfiles submodule 2020-11-18 16:28:34 +00:00
Capa Bot
7aea9fa1d2 Sync capa rules submodule 2020-11-16 19:38:02 +00:00
Capa Bot
5d30be31e0 Sync capa rules submodule 2020-11-16 09:44:08 +00:00
Capa Bot
7abe66e3de Sync capa rules submodule 2020-11-16 06:40:23 +00:00
mike-hunhoff
49ef5e5e64 Merge pull request #364 from fireeye/viv/fix-353
improve viv extractor unicode string detection
2020-11-10 17:56:47 -07:00
Michael Hunhoff
c2266bc105 improve viv extractor unicode string detection with supporting unit test 2020-11-10 12:23:07 -07:00
Moritz
a813e219e6 Merge pull request #363 from fireeye/williballenthin-patch-1
ci: disable py3.9 testing
2020-11-09 21:14:36 +01:00
Moritz
1c1fb20546 Merge pull request #355 from danielplohmann/backend-smda
initial commit for backend-smda
2020-11-09 21:13:51 +01:00
Willi Ballenthin
65feb60bb8 ci: disable py3.9 testing 2020-11-09 13:06:37 -07:00
Daniel Plohmann (jupiter)
f7492c7dc7 throw UnsupportedRuntimeError if SmdaFeatureExtractor is used with a Python version < 3.0 2020-11-09 16:20:08 +01:00
Moritz Raabe
dfc805b89b improvements for PR #355 2020-11-09 13:39:19 +01:00
Moritz Raabe
75defc13a0 disable fail-fast for tests job 2020-11-09 13:22:23 +01:00
Daniel Plohmann (jupiter)
7d4888bb77 addressing the comments in the PR discussion 2020-11-06 10:09:06 +01:00
Daniel Plohmann (jupiter)
1a34029171 Merge branch 'master' of github.com:fireeye/capa into backend-smda 2020-11-06 09:50:09 +01:00
Willi Ballenthin
f6ad4652e4 Merge pull request #358 from fireeye/doc/pyinstaller
document PyInstaller build process
2020-11-05 09:19:51 -07:00
pnx@pyrite
1e25604b0b replacement test for nested x64 thunks - still needs to be verified for vivisect 2020-11-05 16:31:47 +01:00
pnx@pyrite
3a43ffa641 adjusted identification of thunks via SMDA. 2020-11-05 12:58:07 +01:00
Capa Bot
8f6bcf3d98 Sync capa rules submodule 2020-11-03 14:23:36 +00:00
Moritz Raabe
0fd9753681 document PyInstaller build process
closes #357
2020-11-03 15:03:32 +01:00
Capa Bot
76a04dfe25 Sync capa rules submodule 2020-11-03 13:20:30 +00:00
Capa Bot
16317182e3 Sync capa-testfiles submodule 2020-11-03 13:14:45 +00:00
Daniel Plohmann (jupiter)
6bcdf64f67 formatting 2020-10-30 15:34:02 +01:00
Daniel Plohmann (jupiter)
d276a07a71 comments on a test where disassembly differs among backends 2020-10-30 15:29:38 +01:00
Daniel Plohmann (jupiter)
f3b59b342a Merge branch 'backend-smda' of github.com:danielplohmann/capa into backend-smda 2020-10-30 15:25:45 +01:00
Daniel Plohmann (jupiter)
4a0f1f22ba test fixes 2020-10-30 15:25:42 +01:00
Jon Crussell
0c85e7604c use magical derefs
Found derefs in viv/insn.py, does exactly what we need!
2020-10-30 07:23:24 -07:00
Jon Crussell
8f6a46e2d8 add check for pointer to string
Check if memory referenced is a pointer to a string. Fixes mimikatz
string test.
2020-10-30 07:01:07 -07:00
Daniel Plohmann (jupiter)
74b2c18296 down to 14 failed 2020-10-29 20:05:50 +01:00
Jon Crussell
b12d0b6424 tests: add smda backend test
40 failed, 73 passed.
2020-10-29 09:56:28 -07:00
Daniel Plohmann (jupiter)
60ddf0400e addressing review 2020-10-29 17:47:10 +01:00
Daniel Plohmann (jupiter)
669d3484c0 Merge remote-tracking branch 'origin/master' into backend-smda 2020-10-29 17:38:21 +01:00
William Ballenthin
5420ad97a3 sync submodules 2020-10-29 09:42:56 -06:00
Daniel Plohmann (jupiter)
36822926af initial commit for backend-smda 2020-10-29 11:28:22 +01:00
Capa Bot
eef8f2e781 Sync capa rules submodule 2020-10-29 03:50:40 +00:00
Capa Bot
31ac667623 Sync capa rules submodule 2020-10-27 15:16:07 +00:00
Capa Bot
868ceb25bf Sync capa rules submodule 2020-10-27 15:15:30 +00:00
Capa Bot
ee3ab94774 Sync capa rules submodule 2020-10-27 15:15:04 +00:00
Capa Bot
1c47877a8c Sync capa rules submodule 2020-10-27 15:14:22 +00:00
Capa Bot
84698462f3 Sync capa rules submodule 2020-10-27 15:13:25 +00:00
Capa Bot
da7dc793e7 Sync capa rules submodule 2020-10-27 15:12:51 +00:00
Capa Bot
044ee83fbc Sync capa-testfiles submodule 2020-10-26 16:48:15 +00:00
Capa Bot
aea324c4a8 Sync capa rules submodule 2020-10-26 16:47:44 +00:00
Capa Bot
4d05b20830 Sync capa rules submodule 2020-10-26 16:46:53 +00:00
Willi Ballenthin
276928951c build: event published/edited, not created 2020-10-23 15:17:32 -06:00
Willi Ballenthin
9486654e77 changelog: v1.4.1 2020-10-23 15:13:22 -06:00
Willi Ballenthin
2a2b4cbb06 Merge pull request #351 from fireeye/ci-build-windows-vcpython27
fix build on windows-latest
2020-10-23 15:10:56 -06:00
Willi Ballenthin
3ba4a8cdd8 Update build.yml 2020-10-23 15:07:13 -06:00
Willi Ballenthin
8820dabab9 Update build.yml 2020-10-23 14:59:34 -06:00
Willi Ballenthin
f9d89301df Update build.yml 2020-10-23 14:58:44 -06:00
Willi Ballenthin
7edb93d3ad Update build.yml 2020-10-23 14:57:14 -06:00
Moritz
5c5d9974e1 Merge pull request #350 from fireeye/release-1.4.0
release v1.4.0
2020-10-23 22:31:00 +02:00
Moritz Raabe
b0bf4f8f8e prepare new release 2020-10-23 22:24:50 +02:00
Capa Bot
04ea03caf6 Sync capa rules submodule 2020-10-23 18:50:52 +00:00
Capa Bot
cf0841bdcc Sync capa-testfiles submodule 2020-10-23 18:49:05 +00:00
Capa Bot
cc4f5f66d8 Sync capa-testfiles submodule 2020-10-23 18:42:54 +00:00
Capa Bot
e6d75ee7c4 Sync capa rules submodule 2020-10-23 16:46:53 +00:00
Moritz
61986fc98c Merge pull request #333 from fireeye/improve-packaging-setup
add long description and other improvements
2020-10-23 13:16:13 +02:00
Moritz
0e009c7c12 Merge pull request #347 from fireeye/fix/non-ascii-char-filename
get decoded sample path
2020-10-23 13:15:36 +02:00
Moritz
425613ee42 Merge pull request #346 from fireeye/extract/api-jmps
Extract/api jmps
2020-10-23 13:15:10 +02:00
Moritz Raabe
679316946e addressing Willi's feedback 2020-10-22 20:10:47 +02:00
Moritz
8bb305038b Merge pull request #343 from fireeye/fix/file-imports-ordinal-name
extract ordinal and name imports
2020-10-22 20:07:42 +02:00
Moritz Raabe
fbe104d254 get decoded sample path
closes #328
2020-10-22 19:56:41 +02:00
Capa Bot
cb44cb0ee2 Sync capa-testfiles submodule 2020-10-22 17:49:54 +00:00
Capa Bot
2163f64877 Sync capa-testfiles submodule 2020-10-22 17:49:18 +00:00
Capa Bot
a14d958ef0 Sync capa-testfiles submodule 2020-10-22 13:17:55 +00:00
Capa Bot
c65ef12783 Sync capa rules submodule 2020-10-22 04:02:25 +00:00
Capa Bot
8eb1727c76 Sync capa rules submodule 2020-10-21 15:54:41 +00:00
William Ballenthin
fafe24295a Merge branch 'master' of github.com:fireeye/capa 2020-10-21 09:53:09 -06:00
William Ballenthin
d900a6c145 render: default: sanity check MBC 2020-10-21 09:52:40 -06:00
Capa Bot
03df2fa3e9 Sync capa rules submodule 2020-10-21 15:43:31 +00:00
Moritz Raabe
69a4b99d70 extract apis called via jmp
closes #337
2020-10-21 12:39:45 +02:00
Capa Bot
39d95b2fd2 Sync capa rules submodule 2020-10-21 10:21:54 +00:00
Moritz Raabe
1e3b29de2e add IDA specific test 2020-10-21 12:16:50 +02:00
Moritz
d5186f160d Merge pull request #342 from fireeye/viv/extractor/api-thunk-chains
extract api features for thunk chains
2020-10-21 11:37:58 +02:00
Capa Bot
5d7dbd15c7 Sync capa-testfiles submodule 2020-10-21 09:35:22 +00:00
Moritz Raabe
12d5fe0afe addressing feedback 2020-10-21 11:25:08 +02:00
Capa Bot
3df1cc9038 Sync capa rules submodule 2020-10-20 21:04:10 +00:00
Willi Ballenthin
d46152b73e Merge pull request #345 from fireeye/fix/build-workflow-set-env-var
set env var via environment file
2020-10-20 09:55:26 -06:00
Moritz Raabe
9fc6e0d6a2 Merge branch 'enhance/show-features' into viv/extractor/api-thunk-chains 2020-10-20 15:26:51 +02:00
Moritz Raabe
4994d0597f set env var via environment file 2020-10-20 15:14:36 +02:00
Moritz Raabe
76b46d7957 ensure function is defined in vivisect (or do so)
and show features in IDA
2020-10-20 15:09:07 +02:00
Moritz Raabe
0a369c548b extract ordinal and name imports 2020-10-20 14:56:38 +02:00
Moritz Raabe
9a738ba413 extract api features for thunk chains
closes #341
2020-10-20 14:49:09 +02:00
Moritz
a442536246 Merge pull request #340 from fireeye/ida/extractor/improve-api-thunk-detection
ida/extractor: improve detection of APIs called via two or more chained thunks
2020-10-19 20:51:16 +02:00
Capa Bot
f85b6fde7b Sync capa rules submodule 2020-10-16 16:05:56 +00:00
Capa Bot
8dc6a5109a Sync capa-testfiles submodule 2020-10-15 21:00:58 +00:00
Michael Hunhoff
235d9d4ab5 improve detection of APIs called via two or more chained thunks 2020-10-15 14:31:23 -06:00
Capa Bot
3572de058b Sync capa rules submodule 2020-10-08 18:16:59 +00:00
Capa Bot
93068aff1b Sync capa-testfiles submodule 2020-10-08 18:16:15 +00:00
Capa Bot
49e7d75ce5 Sync capa rules submodule 2020-10-08 15:53:20 +00:00
Capa Bot
6aa1ecd1a8 Sync capa-testfiles submodule 2020-10-08 15:52:23 +00:00
Capa Bot
b442fbb19c Sync capa rules submodule 2020-10-07 20:58:02 +00:00
Capa Bot
46fc4f0c25 Sync capa-testfiles submodule 2020-10-07 20:57:34 +00:00
Capa Bot
155de6f2b9 Sync capa rules submodule 2020-10-06 16:30:56 +00:00
Capa Bot
459af7ab1b Sync capa rules submodule 2020-10-06 02:36:03 +00:00
Willi Ballenthin
2bd408a274 Merge pull request #338 from fireeye/fix/feature-str
fix feature display
2020-10-05 14:19:54 -06:00
Moritz Raabe
bc1c5a59f8 display value including 0 2020-10-05 22:10:04 +02:00
Willi Ballenthin
49cecdc75d Merge pull request #336 from fireeye/fix-335
modify find_byte_sequence to yield all locations
2020-10-05 11:02:36 -06:00
Capa Bot
2a6aeae763 Sync capa rules submodule 2020-10-05 17:02:21 +00:00
Michael Hunhoff
f295e1da31 modify find_byte_sequence to yield all locations, instead of only first 2020-10-05 10:27:45 -06:00
Capa Bot
1981859343 Sync capa rules submodule 2020-10-05 16:11:30 +00:00
Capa Bot
9de237e1a3 Sync capa-testfiles submodule 2020-10-05 14:18:32 +00:00
Moritz Raabe
77b412c1e8 add long description and other improvements 2020-10-02 17:08:03 +02:00
Moritz
a31529bb79 Merge pull request #332 from fireeye/render-mbc
render mbc table
2020-10-02 11:09:39 +02:00
Moritz Raabe
00bc1a169e render mbc table 2020-10-01 11:10:03 +02:00
Capa Bot
3e98cac397 Sync capa rules submodule 2020-10-01 09:00:31 +00:00
Capa Bot
8cd0777683 Sync capa rules submodule 2020-10-01 08:32:39 +00:00
Capa Bot
8bac77c2ab Sync capa rules submodule 2020-10-01 07:57:13 +00:00
Capa Bot
3312e1b20b Sync capa rules submodule 2020-09-30 17:27:42 +00:00
Capa Bot
d55e2a2647 Sync capa rules submodule 2020-09-28 15:03:30 +00:00
Willi Ballenthin
e87d9cd1b5 Merge pull request #330 from fireeye/fix-329
fix 329
2020-09-28 09:01:34 -06:00
Michael Hunhoff
5dda95385d use rpartition in capa.features.insn.API to handle API name w/ multiple . 2020-09-28 08:33:08 -06:00
Willi Ballenthin
d60bdb561e Merge pull request #327 from fireeye/fix/312-statement-descriptions
parse descriptions for statements
2020-09-25 11:50:47 -06:00
Capa Bot
fab89beba0 Sync capa rules submodule 2020-09-25 17:49:24 +00:00
Moritz Raabe
1cb9ed9c01 addressing final comments 2020-09-25 18:38:46 +02:00
Moritz Raabe
00b7f2e02f addressing Willi's feedback 2020-09-24 20:23:15 +02:00
Moritz Raabe
4691302a78 parse descriptions for statements 2020-09-24 15:35:30 +02:00
Willi Ballenthin
d8a32630fb Merge pull request #326 from fireeye/fix-325
main: fix reported total rule count
2020-09-23 16:07:22 -06:00
Willi Ballenthin
29b6bd8aad Merge pull request #324 from fireeye/fix-307
scripts: add script demonstrating bulk processing
2020-09-23 14:45:56 -06:00
William Ballenthin
c2516e7453 main: fix reported total rule count
closes #325
2020-09-23 11:19:01 -06:00
Willi Ballenthin
1fd8c3c068 Merge pull request #323 from fireeye/fix-306
use PyYAML CLoader to parse rules when available
2020-09-23 10:01:15 -06:00
William Ballenthin
314757a235 scripts: add script demonstrating bulk processing
closes #307
2020-09-23 09:13:49 -06:00
William Ballenthin
5b613903e5 rules: fix ordering of meta under py2 2020-09-23 06:32:22 -06:00
Capa Bot
b2caad9b4b Sync capa rules submodule 2020-09-22 18:49:29 +00:00
William Ballenthin
4b066e908c ci: use sudo to apt 2020-09-22 11:20:15 -06:00
William Ballenthin
041e443619 ci: install libyaml when appropriate 2020-09-22 11:18:15 -06:00
William Ballenthin
999bd84a86 rules: fall back to python pyyaml when libyaml not present 2020-09-22 11:06:48 -06:00
William Ballenthin
2a894fb5f6 rules: fall back to python based yaml parser when libyaml not present 2020-09-22 10:54:53 -06:00
William Ballenthin
79bf5c2d6b rules: use yaml.CLoader for better performance 2020-09-22 10:46:05 -06:00
Capa Bot
98298a3b2d Sync capa rules submodule 2020-09-21 18:03:51 +00:00
Capa Bot
71454c6400 Sync capa-testfiles submodule 2020-09-21 09:33:08 +00:00
Capa Bot
5e2e316474 Sync capa rules submodule 2020-09-18 20:47:00 +00:00
Capa Bot
6bca211267 Sync capa rules submodule 2020-09-18 18:37:14 +00:00
Moritz
f8cbc0a12d Merge pull request #321 from fireeye/ida/explorer-update-documentation
explorer: documentation updates, logo
2020-09-18 17:03:19 +02:00
Capa Bot
9708c89772 Sync capa rules submodule 2020-09-18 14:26:29 +00:00
Michael Hunhoff
29492bfdc8 fixing feature count for explorer progress indicator 2020-09-17 14:50:14 -06:00
Capa Bot
d2e05f03cc Sync capa rules submodule 2020-09-17 18:34:36 +00:00
Capa Bot
01bf7b3bd3 Sync capa rules submodule 2020-09-17 18:07:50 +00:00
Capa Bot
db790ab20c Sync capa-testfiles submodule 2020-09-17 18:01:18 +00:00
Capa Bot
71c19a1fbc Sync capa rules submodule 2020-09-17 15:02:03 +00:00
Capa Bot
73e9b6e804 Sync capa rules submodule 2020-09-17 15:01:25 +00:00
Michael Hunhoff
199e9fc81d Merge branch 'master' into ida/explorer-update-documentation 2020-09-16 13:55:24 -06:00
Michael Hunhoff
a9591aad1b updating explorer documentation link 2020-09-16 13:53:47 -06:00
Michael Hunhoff
0168f444d9 removing old .jpg, adding explorer logo, updating explorer readme 2020-09-16 13:33:11 -06:00
mike-hunhoff
4659ab0649 Merge pull request #316 from fireeye/fix-315
explorer: add additional check for invalid model index
2020-09-16 08:40:59 -06:00
Michael Hunhoff
49700ffb9f add check for invalid model index, fix 315 2020-09-16 08:27:38 -06:00
Moritz
6c6062d5a8 Update usage.md 2020-09-15 10:31:08 +02:00
Moritz
01e8b198c0 Update installation.md 2020-09-15 10:13:41 +02:00
163 changed files with 22981 additions and 6825 deletions

21
.devcontainer/Dockerfile Normal file
View File

@@ -0,0 +1,21 @@
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.233.0/containers/python-3/.devcontainer/base.Dockerfile
# [Choice] Python version (use -bullseye variants on local arm64/Apple Silicon): 3, 3.10, 3.9, 3.8, 3.7, 3.6, 3-bullseye, 3.10-bullseye, 3.9-bullseye, 3.8-bullseye, 3.7-bullseye, 3.6-bullseye, 3-buster, 3.10-buster, 3.9-buster, 3.8-buster, 3.7-buster, 3.6-buster
ARG VARIANT="3.10-bullseye"
FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT}
# [Choice] Node.js version: none, lts/*, 16, 14, 12, 10
ARG NODE_VERSION="none"
RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi
# [Optional] If your pip requirements rarely change, uncomment this section to add them to the image.
# COPY requirements.txt /tmp/pip-tmp/
# RUN pip3 --disable-pip-version-check --no-cache-dir install -r /tmp/pip-tmp/requirements.txt \
# && rm -rf /tmp/pip-tmp
# [Optional] Uncomment this section to install additional OS packages.
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
# && apt-get -y install --no-install-recommends <your-package-list-here>
# [Optional] Uncomment this line to install global node packages.
# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g <your-package-here>" 2>&1

View File

@@ -0,0 +1,51 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.233.0/containers/python-3
{
"name": "Python 3",
"build": {
"dockerfile": "Dockerfile",
"context": "..",
"args": {
// Update 'VARIANT' to pick a Python version: 3, 3.10, 3.9, 3.8, 3.7, 3.6
// Append -bullseye or -buster to pin to an OS version.
// Use -bullseye variants on local on arm64/Apple Silicon.
"VARIANT": "3.10",
// Options
"NODE_VERSION": "none"
}
},
// Set *default* container specific settings.json values on container create.
"settings": {
"python.defaultInterpreterPath": "/usr/local/bin/python",
"python.linting.enabled": true,
"python.linting.pylintEnabled": true,
"python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8",
"python.formatting.blackPath": "/usr/local/py-utils/bin/black",
"python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf",
"python.linting.banditPath": "/usr/local/py-utils/bin/bandit",
"python.linting.flake8Path": "/usr/local/py-utils/bin/flake8",
"python.linting.mypyPath": "/usr/local/py-utils/bin/mypy",
"python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle",
"python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle",
"python.linting.pylintPath": "/usr/local/py-utils/bin/pylint"
},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"ms-python.python",
"ms-python.vscode-pylance"
],
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "git submodule update --init && pip3 install --user -e .[dev]",
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "vscode",
"features": {
"git": "latest"
}
}

9
.gitattributes vendored Normal file
View File

@@ -0,0 +1,9 @@
# Set the default behavior, in case people don't have core.autocrlf set.
* text=auto
# Explicitly declare text files you want to always be normalized and converted
# to native line endings on checkout.
*.py text
*.yml text
*.md text
*.txt text

View File

@@ -1,46 +1,46 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [https://contributor-covenant.org/version/1/4][version]
[homepage]: https://contributor-covenant.org
[version]: https://contributor-covenant.org/version/1/4/
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [https://contributor-covenant.org/version/1/4][version]
[homepage]: https://contributor-covenant.org
[version]: https://contributor-covenant.org/version/1/4/

View File

@@ -1,197 +1,197 @@
# Contributing to Capa
First off, thanks for taking the time to contribute!
The following is a set of guidelines for contributing to capa and its packages, which are hosted in the [FireEye Organization](https://github.com/fireeye) on GitHub. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request.
#### Table Of Contents
[Code of Conduct](#code-of-conduct)
[What should I know before I get started?](#what-should-i-know-before-i-get-started)
* [Capa and its Repositories](#capa-and-its-repositories)
* [Capa Design Decisions](#design-decisions)
[How Can I Contribute?](#how-can-i-contribute)
* [Reporting Bugs](#reporting-bugs)
* [Suggesting Enhancements](#suggesting-enhancements)
* [Your First Code Contribution](#your-first-code-contribution)
* [Pull Requests](#pull-requests)
[Styleguides](#styleguides)
* [Git Commit Messages](#git-commit-messages)
* [Python Styleguide](#python-styleguide)
* [Rules Styleguide](#rules-styleguide)
## Code of Conduct
This project and everyone participating in it is governed by the [Capa Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to the maintainers.
## What should I know before I get started?
### Capa and its repositories
We host the capa project as three Github repositories:
- [capa](https://github.com/fireeye/capa)
- [capa-rules](https://github.com/fireeye/capa-rules)
- [capa-testfiles](https://github.com/fireeye/capa-testfiles)
The command line tools, logic engine, and other Python source code are found in the `capa` repository.
This is the repository to fork when you want to enhance the features, performance, or user interface of capa.
Do *not* push rules directly to this repository, instead...
The standard rules contributed by the community are found in the `capa-rules` repository.
When you have an idea for a new rule, you should open a PR against `capa-rules`.
We keep `capa` and `capa-rules` separate to distinguish where ideas, bugs, and discussions should happen.
If you're writing yaml it probably goes in `capa-rules` and if you're writing Python it probably goes in `capa`.
Also, we encourage users to develop their own rule repositories, so we treat our default set of rules in the same way.
Test fixtures, such as malware samples and analysis workspaces, are found in the `capa-testfiles` repository.
These are files you'll need in order to run the linter (in `--thorough` mode) and full test suites;
however, they take up a lot of space (1GB+), so by keeping `capa-testfiles` separate,
a shallow checkout of `capa` and `capa-rules` doesn't take much bandwidth.
### Design Decisions
When we make a significant decision in how we maintain the project and what we can or cannot support,
we will document it in the [capa issues tracker](https://github.com/fireeye/capa/issues).
This is the best place review our discussions about what/how/why we do things in the project.
If you have a question, check to see if it is documented there.
If it is *not* documented there, or you can't find an answer, please open a issue.
We'll link to existing issues when appropriate to keep discussions in one place.
## How Can I Contribute?
### Reporting Bugs
This section guides you through submitting a bug report for capa.
Following these guidelines helps maintainers and the community understand your report, reproduce the behavior, and find related reports.
Before creating bug reports, please check [this list](#before-submitting-a-bug-report)
as you might find out that you don't need to create one.
When you are creating a bug report, please [include as many details as possible](#how-do-i-submit-a-good-bug-report).
Fill out [the required template](./ISSUE_TEMPLATE/bug_report.md),
the information it asks for helps us resolve issues faster.
> **Note:** If you find a **Closed** issue that seems like it is the same thing that you're experiencing, open a new issue and include a link to the original issue in the body of your new one.
#### Before Submitting A Bug Report
* **Determine [which repository the problem should be reported in](#capa-and-its-repositories)**.
* **Perform a [cursory search](https://github.com/fireeye/capa/issues?q=is%3Aissue)** to see if the problem has already been reported. If it has **and the issue is still open**, add a comment to the existing issue instead of opening a new one.
#### How Do I Submit A (Good) Bug Report?
Bugs are tracked as [GitHub issues](https://guides.github.com/features/issues/).
After you've determined [which repository](#capa-and-its-repositories) your bug is related to,
create an issue on that repository and provide the following information by filling in
[the template](./ISSUE_TEMPLATE/bug_report.md).
Explain the problem and include additional details to help maintainers reproduce the problem:
* **Use a clear and descriptive title** for the issue to identify the problem.
* **Describe the exact steps which reproduce the problem** in as many details as possible. For example, start by explaining how you started capa, e.g. which command exactly you used in the terminal, or how you started capa otherwise.
* **Provide specific examples to demonstrate the steps**. Include links to files or GitHub projects, or copy/pasteable snippets, which you use in those examples. If you're providing snippets in the issue, use [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines).
* **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior.
* **Explain which behavior you expected to see instead and why.**
* **Include screenshots and animated GIFs** which show you following the described steps and clearly demonstrate the problem. You can use [this tool](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux.
* **If you're reporting that capa crashed**, include the stack trace from the terminal. Include the stack trace in the issue in a [code block](https://help.github.com/articles/markdown-basics/#multiple-lines), a [file attachment](https://help.github.com/articles/file-attachments-on-issues-and-pull-requests/), or put it in a [gist](https://gist.github.com/) and provide link to that gist.
* **If the problem wasn't triggered by a specific action**, describe what you were doing before the problem happened and share more information using the guidelines below.
Provide more context by answering these questions:
* **Did the problem start happening recently** (e.g. after updating to a new version of capa) or was this always a problem?
* If the problem started happening recently, **can you reproduce the problem in an older version of capa?** What's the most recent version in which the problem doesn't happen? You can download older versions of capa from [the releases page](https://github.com/fireeye/capa/releases).
* **Can you reliably reproduce the issue?** If not, provide details about how often the problem happens and under which conditions it normally happens.
* If the problem is related to working with files (e.g. opening and editing files), **does the problem happen for all files and projects or only some?** Does the problem happen only when working with local or remote files (e.g. on network drives), with files of a specific type (e.g. only JavaScript or Python files), with large files or files with very long lines, or with files in a specific encoding? Is there anything else special about the files you are using?
Include details about your configuration and environment:
* **Which version of capa are you using?** You can get the exact version by running `capa --version` in your terminal.
* **What's the name and version of the OS you're using**?
### Suggesting Enhancements
This section guides you through submitting an enhancement suggestion for capa, including completely new features and minor improvements to existing functionality. Following these guidelines helps maintainers and the community understand your suggestion and find related suggestions.
Before creating enhancement suggestions, please check [this list](#before-submitting-an-enhancement-suggestion) as you might find out that you don't need to create one. When you are creating an enhancement suggestion, please [include as many details as possible](#how-do-i-submit-a-good-enhancement-suggestion). Fill in [the template](./ISSUE_TEMPLATE/feature_request.md), including the steps that you imagine you would take if the feature you're requesting existed.
#### Before Submitting An Enhancement Suggestion
* **Determine [which repository the enhancement should be suggested in](#capa-and-its-repositories).**
* **Perform a [cursory search](https://github.com/fireeye/capa/issues?q=is%3Aissue)** to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one.
#### How Do I Submit A (Good) Enhancement Suggestion?
Enhancement suggestions are tracked as [GitHub issues](https://guides.github.com/features/issues/). After you've determined [which repository](#capa-and-its-repositories) your enhancement suggestion is related to, create an issue on that repository and provide the following information:
* **Use a clear and descriptive title** for the issue to identify the suggestion.
* **Provide a step-by-step description of the suggested enhancement** in as many details as possible.
* **Provide specific examples to demonstrate the steps**. Include copy/pasteable snippets which you use in those examples, as [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines).
* **Describe the current behavior** and **explain which behavior you expected to see instead** and why.
* **Include screenshots and animated GIFs** which help you demonstrate the steps or point out the part of capa which the suggestion is related to. You can use [this tool](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux.
* **Explain why this enhancement would be useful** to most capa users and isn't something that can or should be implemented as an external tool that uses capa as a library.
* **Specify which version of capa you're using.** You can get the exact version by running `capa --version` in your terminal.
* **Specify the name and version of the OS you're using.**
### Your First Code Contribution
Unsure where to begin contributing to capa? You can start by looking through these `good-first-issue` and `rule-idea` issues:
* [good-first-issue](https://github.com/fireeye/capa/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) - issues which should only require a few lines of code, and a test or two.
* [rule-idea](https://github.com/fireeye/capa-rules/issues?q=is%3Aissue+is%3Aopen+label%3A%22rule+idea%22) - issues that describe potential new rule ideas.
Both issue lists are sorted by total number of comments. While not perfect, number of comments is a reasonable proxy for impact a given change will have.
#### Local development
capa and all its resources can be developed locally.
For instructions on how to do this, see the "Method 3" section of the [installation guide](https://github.com/fireeye/capa/blob/master/doc/installation.md).
### Pull Requests
The process described here has several goals:
- Maintain capa's quality
- Fix problems that are important to users
- Engage the community in working toward the best possible capa
- Enable a sustainable system for capa's maintainers to review contributions
Please follow these steps to have your contribution considered by the maintainers:
1. Follow all instructions in [the template](PULL_REQUEST_TEMPLATE.md)
2. Follow the [styleguides](#styleguides)
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.
## Styleguides
### Git Commit Messages
* Use the present tense ("Add feature" not "Added feature")
* Use the imperative mood ("Move cursor to..." not "Moves cursor to...")
* Prefix the first line with the component in question ("rules: ..." or "render: ...")
* Reference issues and pull requests liberally after the first line
### Python Styleguide
All Python code must adhere to the style guide used by capa:
1. [PEP8](https://www.python.org/dev/peps/pep-0008/), with clarifications from
2. [Willi's style guide](https://docs.google.com/document/d/1iRpeg-w4DtibwytUyC_dDT7IGhNGBP25-nQfuBa-Fyk/edit?usp=sharing), formatted with
3. [isort](https://pypi.org/project/isort/) (with line width 120 and ordered by line length), and formatted with
4. [black](https://github.com/psf/black) (with line width 120), and formatted with
5. [dos2unix](https://linux.die.net/man/1/dos2unix)
Our CI pipeline will reformat and enforce the Python styleguide.
### Rules Styleguide
All (non-nursery) capa rules must:
1. pass the [linter](https://github.com/fireeye/capa/blob/master/scripts/lint.py), and
2. be formatted with [capafmt](https://github.com/fireeye/capa/blob/master/scripts/capafmt.py)
This ensures that all rules meet the same minimum level of quality and are structured in a consistent way.
Our CI pipeline will reformat and enforce the capa rules styleguide.
# Contributing to Capa
First off, thanks for taking the time to contribute!
The following is a set of guidelines for contributing to capa and its packages, which are hosted in the [Mandiant Organization](https://github.com/mandiant) on GitHub. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request.
#### Table Of Contents
[Code of Conduct](#code-of-conduct)
[What should I know before I get started?](#what-should-i-know-before-i-get-started)
* [Capa and its Repositories](#capa-and-its-repositories)
* [Capa Design Decisions](#design-decisions)
[How Can I Contribute?](#how-can-i-contribute)
* [Reporting Bugs](#reporting-bugs)
* [Suggesting Enhancements](#suggesting-enhancements)
* [Your First Code Contribution](#your-first-code-contribution)
* [Pull Requests](#pull-requests)
[Styleguides](#styleguides)
* [Git Commit Messages](#git-commit-messages)
* [Python Styleguide](#python-styleguide)
* [Rules Styleguide](#rules-styleguide)
## Code of Conduct
This project and everyone participating in it is governed by the [Capa Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to the maintainers.
## What should I know before I get started?
### Capa and its repositories
We host the capa project as three GitHub repositories:
- [capa](https://github.com/mandiant/capa)
- [capa-rules](https://github.com/mandiant/capa-rules)
- [capa-testfiles](https://github.com/mandiant/capa-testfiles)
The command line tools, logic engine, and other Python source code are found in the `capa` repository.
This is the repository to fork when you want to enhance the features, performance, or user interface of capa.
Do *not* push rules directly to this repository, instead...
The standard rules contributed by the community are found in the `capa-rules` repository.
When you have an idea for a new rule, you should open a PR against `capa-rules`.
We keep `capa` and `capa-rules` separate to distinguish where ideas, bugs, and discussions should happen.
If you're writing yaml it probably goes in `capa-rules` and if you're writing Python it probably goes in `capa`.
Also, we encourage users to develop their own rule repositories, so we treat our default set of rules in the same way.
Test fixtures, such as malware samples and analysis workspaces, are found in the `capa-testfiles` repository.
These are files you'll need in order to run the linter (in `--thorough` mode) and full test suites;
however, they take up a lot of space (1GB+), so by keeping `capa-testfiles` separate,
a shallow checkout of `capa` and `capa-rules` doesn't take much bandwidth.
### Design Decisions
When we make a significant decision in how we maintain the project and what we can or cannot support,
we will document it in the [capa issues tracker](https://github.com/mandiant/capa/issues).
This is the best place review our discussions about what/how/why we do things in the project.
If you have a question, check to see if it is documented there.
If it is *not* documented there, or you can't find an answer, please open a issue.
We'll link to existing issues when appropriate to keep discussions in one place.
## How Can I Contribute?
### Reporting Bugs
This section guides you through submitting a bug report for capa.
Following these guidelines helps maintainers and the community understand your report, reproduce the behavior, and find related reports.
Before creating bug reports, please check [this list](#before-submitting-a-bug-report)
as you might find out that you don't need to create one.
When you are creating a bug report, please [include as many details as possible](#how-do-i-submit-a-good-bug-report).
Fill out [the required template](./ISSUE_TEMPLATE/bug_report.md),
the information it asks for helps us resolve issues faster.
> **Note:** If you find a **Closed** issue that seems like it is the same thing that you're experiencing, open a new issue and include a link to the original issue in the body of your new one.
#### Before Submitting A Bug Report
* **Determine [which repository the problem should be reported in](#capa-and-its-repositories)**.
* **Perform a [cursory search](https://github.com/mandiant/capa/issues?q=is%3Aissue)** to see if the problem has already been reported. If it has **and the issue is still open**, add a comment to the existing issue instead of opening a new one.
#### How Do I Submit A (Good) Bug Report?
Bugs are tracked as [GitHub issues](https://guides.github.com/features/issues/).
After you've determined [which repository](#capa-and-its-repositories) your bug is related to,
create an issue on that repository and provide the following information by filling in
[the template](./ISSUE_TEMPLATE/bug_report.md).
Explain the problem and include additional details to help maintainers reproduce the problem:
* **Use a clear and descriptive title** for the issue to identify the problem.
* **Describe the exact steps which reproduce the problem** in as many details as possible. For example, start by explaining how you started capa, e.g. which command exactly you used in the terminal, or how you started capa otherwise.
* **Provide specific examples to demonstrate the steps**. Include links to files or GitHub projects, or copy/pasteable snippets, which you use in those examples. If you're providing snippets in the issue, use [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines).
* **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior.
* **Explain which behavior you expected to see instead and why.**
* **Include screenshots and animated GIFs** which show you following the described steps and clearly demonstrate the problem. You can use [this tool](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux.
* **If you're reporting that capa crashed**, include the stack trace from the terminal. Include the stack trace in the issue in a [code block](https://help.github.com/articles/markdown-basics/#multiple-lines), a [file attachment](https://help.github.com/articles/file-attachments-on-issues-and-pull-requests/), or put it in a [gist](https://gist.github.com/) and provide link to that gist.
* **If the problem wasn't triggered by a specific action**, describe what you were doing before the problem happened and share more information using the guidelines below.
Provide more context by answering these questions:
* **Did the problem start happening recently** (e.g. after updating to a new version of capa) or was this always a problem?
* If the problem started happening recently, **can you reproduce the problem in an older version of capa?** What's the most recent version in which the problem doesn't happen? You can download older versions of capa from [the releases page](https://github.com/mandiant/capa/releases).
* **Can you reliably reproduce the issue?** If not, provide details about how often the problem happens and under which conditions it normally happens.
* If the problem is related to working with files (e.g. opening and editing files), **does the problem happen for all files and projects or only some?** Does the problem happen only when working with local or remote files (e.g. on network drives), with files of a specific type (e.g. only JavaScript or Python files), with large files or files with very long lines, or with files in a specific encoding? Is there anything else special about the files you are using?
Include details about your configuration and environment:
* **Which version of capa are you using?** You can get the exact version by running `capa --version` in your terminal.
* **What's the name and version of the OS you're using**?
### Suggesting Enhancements
This section guides you through submitting an enhancement suggestion for capa, including completely new features and minor improvements to existing functionality. Following these guidelines helps maintainers and the community understand your suggestion and find related suggestions.
Before creating enhancement suggestions, please check [this list](#before-submitting-an-enhancement-suggestion) as you might find out that you don't need to create one. When you are creating an enhancement suggestion, please [include as many details as possible](#how-do-i-submit-a-good-enhancement-suggestion). Fill in [the template](./ISSUE_TEMPLATE/feature_request.md), including the steps that you imagine you would take if the feature you're requesting existed.
#### Before Submitting An Enhancement Suggestion
* **Determine [which repository the enhancement should be suggested in](#capa-and-its-repositories).**
* **Perform a [cursory search](https://github.com/mandiant/capa/issues?q=is%3Aissue)** to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one.
#### How Do I Submit A (Good) Enhancement Suggestion?
Enhancement suggestions are tracked as [GitHub issues](https://guides.github.com/features/issues/). After you've determined [which repository](#capa-and-its-repositories) your enhancement suggestion is related to, create an issue on that repository and provide the following information:
* **Use a clear and descriptive title** for the issue to identify the suggestion.
* **Provide a step-by-step description of the suggested enhancement** in as many details as possible.
* **Provide specific examples to demonstrate the steps**. Include copy/pasteable snippets which you use in those examples, as [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines).
* **Describe the current behavior** and **explain which behavior you expected to see instead** and why.
* **Include screenshots and animated GIFs** which help you demonstrate the steps or point out the part of capa which the suggestion is related to. You can use [this tool](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux.
* **Explain why this enhancement would be useful** to most capa users and isn't something that can or should be implemented as an external tool that uses capa as a library.
* **Specify which version of capa you're using.** You can get the exact version by running `capa --version` in your terminal.
* **Specify the name and version of the OS you're using.**
### Your First Code Contribution
Unsure where to begin contributing to capa? You can start by looking through these `good-first-issue` and `rule-idea` issues:
* [good-first-issue](https://github.com/mandiant/capa/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) - issues which should only require a few lines of code, and a test or two.
* [rule-idea](https://github.com/mandiant/capa-rules/issues?q=is%3Aissue+is%3Aopen+label%3A%22rule+idea%22) - issues that describe potential new rule ideas.
Both issue lists are sorted by total number of comments. While not perfect, number of comments is a reasonable proxy for impact a given change will have.
#### Local development
capa and all its resources can be developed locally.
For instructions on how to do this, see the "Method 3" section of the [installation guide](https://github.com/mandiant/capa/blob/master/doc/installation.md).
### Pull Requests
The process described here has several goals:
- Maintain capa's quality
- Fix problems that are important to users
- Engage the community in working toward the best possible capa
- Enable a sustainable system for capa's maintainers to review contributions
Please follow these steps to have your contribution considered by the maintainers:
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.
## Styleguides
### Git Commit Messages
* Use the present tense ("Add feature" not "Added feature")
* Use the imperative mood ("Move cursor to..." not "Moves cursor to...")
* Prefix the first line with the component in question ("rules: ..." or "render: ...")
* Reference issues and pull requests liberally after the first line
### Python Styleguide
All Python code must adhere to the style guide used by capa:
1. [PEP8](https://www.python.org/dev/peps/pep-0008/), with clarifications from
2. [Willi's style guide](https://docs.google.com/document/d/1iRpeg-w4DtibwytUyC_dDT7IGhNGBP25-nQfuBa-Fyk/edit?usp=sharing), formatted with
3. [isort](https://pypi.org/project/isort/) (with line width 120 and ordered by line length), and formatted with
4. [black](https://github.com/psf/black) (with line width 120), and formatted with
5. [dos2unix](https://linux.die.net/man/1/dos2unix)
Our CI pipeline will reformat and enforce the Python styleguide.
### Rules Styleguide
All (non-nursery) capa rules must:
1. pass the [linter](https://github.com/mandiant/capa/blob/master/scripts/lint.py), and
2. be formatted with [capafmt](https://github.com/mandiant/capa/blob/master/scripts/capafmt.py)
This ensures that all rules meet the same minimum level of quality and are structured in a consistent way.
Our CI pipeline will reformat and enforce the capa rules styleguide.

View File

@@ -1,47 +1,47 @@
---
name: Bug report
about: Create a report to help us improve
---
<!--
# Is your bug report related to capa rules (for example a false positive)?
We use sybmodules to separate code, rules and test data. If your issue is related to capa rules, please report it at https://github.com/fireeye/capa-rules/issues.
# Have you checked that your issue isn't already filed?
Please search if there is a similar issue at https://github.com/fireeye/capa/issues. If there is already a similar issue, please add more details there instead of opening a new one.
# Have you read capa's Code of Conduct?
By filing an Issue, you are expected to comply with it, including treating everyone with respect: https://github.com/fireeye/capa/blob/master/.github/CODE_OF_CONDUCT.md
# Have you read capa's CONTRIBUTING guide?
It contains helpful information about how to contribute to capa. Check https://github.com/fireeye/capa/blob/master/.github/CONTRIBUTING.md#reporting-bugs
-->
### Description
<!-- Description of the issue -->
### Steps to Reproduce
<!-- 1. First Step -->
<!-- 2. Second Step -->
<!-- 3. and so on… -->
**Expected behavior:**
<!-- What you expect to happen -->
**Actual behavior:**
<!-- What actually happens -->
### Versions
<!-- You can get this information from copy and pasting the output of `capa --version` from the command line.
Please specify the component you're using (e.g. standalone tool or IDA Pro integration) and your Python version.
Also, please include the OS and what version of the OS you're running. -->
### Additional Information
<!-- Any additional information, configuration or data that might be necessary to reproduce the issue. -->
---
name: Bug report
about: Create a report to help us improve
---
<!--
# Is your bug report related to capa rules (for example a false positive)?
We use submodules to separate code, rules and test data. If your issue is related to capa rules, please report it at https://github.com/mandiant/capa-rules/issues.
# Have you checked that your issue isn't already filed?
Please search if there is a similar issue at https://github.com/mandiant/capa/issues. If there is already a similar issue, please add more details there instead of opening a new one.
# Have you read capa's Code of Conduct?
By filing an Issue, you are expected to comply with it, including treating everyone with respect: https://github.com/mandiant/capa/blob/master/.github/CODE_OF_CONDUCT.md
# Have you read capa's CONTRIBUTING guide?
It contains helpful information about how to contribute to capa. Check https://github.com/mandiant/capa/blob/master/.github/CONTRIBUTING.md#reporting-bugs
-->
### Description
<!-- Description of the issue -->
### Steps to Reproduce
<!-- 1. First Step -->
<!-- 2. Second Step -->
<!-- 3. and so on… -->
**Expected behavior:**
<!-- What you expect to happen -->
**Actual behavior:**
<!-- What actually happens -->
### Versions
<!-- You can get this information from copy and pasting the output of `capa --version` from the command line.
Please specify the component you're using (e.g. standalone tool or IDA Pro integration) and your Python version.
Also, please include the OS and what version of the OS you're running. -->
### Additional Information
<!-- Any additional information, configuration or data that might be necessary to reproduce the issue. -->

View File

@@ -1,35 +1,35 @@
---
name: Feature request
about: Suggest an idea for capa
---
<!--
# Is your issue related to capa rules (for example an idea for a new rule)?
We use sybmodules to separate code, rules and test data. If your issue is related to capa rules, please report it at https://github.com/fireeye/capa-rules/issues.
# Have you checked that your issue isn't already filed?
Please search if there is a similar issue at https://github.com/fireeye/capa/issues. If there is already a similar issue, please add more details there instead of opening a new one.
# Have you read capa's Code of Conduct?
By filing an Issue, you are expected to comply with it, including treating everyone with respect: https://github.com/fireeye/capa/blob/master/.github/CODE_OF_CONDUCT.md
# Have you read capa's CONTRIBUTING guide?
It contains helpful information about how to contribute to capa. Check https://github.com/fireeye/capa/blob/master/.github/CONTRIBUTING.md#suggesting-enhancements
-->
### Summary
<!-- One paragraph explanation of the feature. -->
### Motivation
<!-- Why are we doing this? What use cases does it support? What is the expected outcome? -->
### Describe alternatives you've considered
<!-- A clear and concise description of the alternative solutions you've considered. -->
## Additional context
<!-- Add any other context or screenshots about the feature request here. -->
---
name: Feature request
about: Suggest an idea for capa
---
<!--
# Is your issue related to capa rules (for example an idea for a new rule)?
We use submodules to separate code, rules and test data. If your issue is related to capa rules, please report it at https://github.com/mandiant/capa-rules/issues.
# Have you checked that your issue isn't already filed?
Please search if there is a similar issue at https://github.com/mandiant/capa/issues. If there is already a similar issue, please add more details there instead of opening a new one.
# Have you read capa's Code of Conduct?
By filing an Issue, you are expected to comply with it, including treating everyone with respect: https://github.com/mandiant/capa/blob/master/.github/CODE_OF_CONDUCT.md
# Have you read capa's CONTRIBUTING guide?
It contains helpful information about how to contribute to capa. Check https://github.com/mandiant/capa/blob/master/.github/CONTRIBUTING.md#suggesting-enhancements
-->
### Summary
<!-- One paragraph explanation of the feature. -->
### Motivation
<!-- Why are we doing this? What use cases does it support? What is the expected outcome? -->
### Describe alternatives you've considered
<!-- A clear and concise description of the alternative solutions you've considered. -->
## Additional context
<!-- Add any other context or screenshots about the feature request here. -->

BIN
.github/capa-explorer-logo.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

BIN
.github/capa-ida.jpg vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 453 KiB

6
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "weekly"

85
.github/mypy/mypy.ini vendored Normal file
View File

@@ -0,0 +1,85 @@
[mypy]
[mypy-halo.*]
ignore_missing_imports = True
[mypy-tqdm.*]
ignore_missing_imports = True
[mypy-ruamel.*]
ignore_missing_imports = True
[mypy-networkx.*]
ignore_missing_imports = True
[mypy-pefile.*]
ignore_missing_imports = True
[mypy-viv_utils.*]
ignore_missing_imports = True
[mypy-flirt.*]
ignore_missing_imports = True
[mypy-lief.*]
ignore_missing_imports = True
[mypy-idc.*]
ignore_missing_imports = True
[mypy-vivisect.*]
ignore_missing_imports = True
[mypy-envi.*]
ignore_missing_imports = True
[mypy-PE.*]
ignore_missing_imports = True
[mypy-idaapi.*]
ignore_missing_imports = True
[mypy-idautils.*]
ignore_missing_imports = True
[mypy-ida_bytes.*]
ignore_missing_imports = True
[mypy-ida_nalt.*]
ignore_missing_imports = True
[mypy-ida_kernwin.*]
ignore_missing_imports = True
[mypy-ida_settings.*]
ignore_missing_imports = True
[mypy-ida_funcs.*]
ignore_missing_imports = True
[mypy-ida_loader.*]
ignore_missing_imports = True
[mypy-ida_segment.*]
ignore_missing_imports = True
[mypy-PyQt5.*]
ignore_missing_imports = True
[mypy-binaryninja.*]
ignore_missing_imports = True
[mypy-pytest.*]
ignore_missing_imports = True
[mypy-devtools.*]
ignore_missing_imports = True
[mypy-elftools.*]
ignore_missing_imports = True
[mypy-dncil.*]
ignore_missing_imports = True
[mypy-netnode.*]
ignore_missing_imports = True

22
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,22 @@
<!--
Thank you for contributing to capa! <3
Please read capa's CONTRIBUTING guide if you haven't done so already.
It contains helpful information about how to contribute to capa. Check https://github.com/mandiant/capa/blob/master/.github/CONTRIBUTING.md
Please describe the changes in this pull request (PR). Include your motivation and context to help us review.
Please mention the issue your PR addresses (if any):
closes #issue_number
-->
### Checklist
<!-- CHANGELOG.md has a `master (unreleased)` section. Please add bug fixes, new features, breaking changes and anything else you think is worthwhile mentioning in the release notes to this file. -->
- [ ] No CHANGELOG update needed
<!-- Tests prove that your fix/work as expected and ensure it doesn't break on the feature. -->
- [ ] No new tests needed
<!-- Please help us keeping capa documentation up-to-date -->
- [ ] No documentation update needed

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
from PyInstaller.utils.hooks import copy_metadata
@@ -13,3 +13,144 @@ from PyInstaller.utils.hooks import copy_metadata
#
# ref: https://github.com/pyinstaller/pyinstaller/issues/1713#issuecomment-162682084
datas = copy_metadata("vivisect")
excludedimports = [
# viv gui requires these heavy libraries,
# but viv as a library doesn't.
# they shouldn't be installed in our configuration,
# but we'll ensure they don't slip in here (such as on developers' systems).
"PyQt5",
"qt5",
"pyqtwebengine",
# the above are imported by these viv modules.
# so really, we'd want to exclude these submodules of viv.
# but i dont think this works.
"vqt",
"vdb.qt",
"envi.qt",
# unused by capa
"pyasn1",
]
hiddenimports = [
# vivisect does manual/runtime importing of its modules,
# so declare the things that could be imported here.
"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.mkpointers",
"vivisect.analysis.generic.pointers",
"vivisect.analysis.generic.pointertables",
"vivisect.analysis.generic.relocations",
"vivisect.analysis.generic.strconst",
"vivisect.analysis.generic.switchcase",
"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.ms",
"vivisect.analysis.ms.hotpatch",
"vivisect.analysis.ms.localhints",
"vivisect.analysis.ms.msvc",
"vivisect.analysis.ms.msvcfunc",
"vivisect.analysis.ms.vftables",
"vivisect.analysis.pe",
"vivisect.impapi.posix.amd64",
"vivisect.impapi.posix.i386",
"vivisect.impapi.windows",
"vivisect.impapi.windows.amd64",
"vivisect.impapi.windows.i386",
"vivisect.impapi.winkern.i386",
"vivisect.impapi.winkern.amd64",
"vivisect.parsers.blob",
"vivisect.parsers.elf",
"vivisect.parsers.ihex",
"vivisect.parsers.macho",
"vivisect.parsers.pe",
"vivisect.storage",
"vivisect.storage.basicfile",
"vstruct.constants",
"vstruct.constants.ntstatus",
"vstruct.defs",
"vstruct.defs.arm7",
"vstruct.defs.bmp",
"vstruct.defs.dns",
"vstruct.defs.elf",
"vstruct.defs.gif",
"vstruct.defs.ihex",
"vstruct.defs.inet",
"vstruct.defs.java",
"vstruct.defs.kdcom",
"vstruct.defs.macho",
"vstruct.defs.macho.const",
"vstruct.defs.macho.fat",
"vstruct.defs.macho.loader",
"vstruct.defs.macho.stabs",
"vstruct.defs.minidump",
"vstruct.defs.pcap",
"vstruct.defs.pe",
"vstruct.defs.pptp",
"vstruct.defs.rar",
"vstruct.defs.swf",
"vstruct.defs.win32",
"vstruct.defs.windows",
"vstruct.defs.windows.win_5_1_i386",
"vstruct.defs.windows.win_5_1_i386.ntdll",
"vstruct.defs.windows.win_5_1_i386.ntoskrnl",
"vstruct.defs.windows.win_5_1_i386.win32k",
"vstruct.defs.windows.win_5_2_i386",
"vstruct.defs.windows.win_5_2_i386.ntdll",
"vstruct.defs.windows.win_5_2_i386.ntoskrnl",
"vstruct.defs.windows.win_5_2_i386.win32k",
"vstruct.defs.windows.win_6_1_amd64",
"vstruct.defs.windows.win_6_1_amd64.ntdll",
"vstruct.defs.windows.win_6_1_amd64.ntoskrnl",
"vstruct.defs.windows.win_6_1_amd64.win32k",
"vstruct.defs.windows.win_6_1_i386",
"vstruct.defs.windows.win_6_1_i386.ntdll",
"vstruct.defs.windows.win_6_1_i386.ntoskrnl",
"vstruct.defs.windows.win_6_1_i386.win32k",
"vstruct.defs.windows.win_6_1_wow64",
"vstruct.defs.windows.win_6_1_wow64.ntdll",
"vstruct.defs.windows.win_6_2_amd64",
"vstruct.defs.windows.win_6_2_amd64.ntdll",
"vstruct.defs.windows.win_6_2_amd64.ntoskrnl",
"vstruct.defs.windows.win_6_2_amd64.win32k",
"vstruct.defs.windows.win_6_2_i386",
"vstruct.defs.windows.win_6_2_i386.ntdll",
"vstruct.defs.windows.win_6_2_i386.ntoskrnl",
"vstruct.defs.windows.win_6_2_i386.win32k",
"vstruct.defs.windows.win_6_2_wow64",
"vstruct.defs.windows.win_6_2_wow64.ntdll",
"vstruct.defs.windows.win_6_3_amd64",
"vstruct.defs.windows.win_6_3_amd64.ntdll",
"vstruct.defs.windows.win_6_3_amd64.ntoskrnl",
"vstruct.defs.windows.win_6_3_i386",
"vstruct.defs.windows.win_6_3_i386.ntdll",
"vstruct.defs.windows.win_6_3_i386.ntoskrnl",
"vstruct.defs.windows.win_6_3_wow64",
"vstruct.defs.windows.win_6_3_wow64.ntdll",
]

View File

@@ -1,176 +1,40 @@
# -*- mode: python -*-
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
import os.path
import subprocess
import wcwidth
# when invoking pyinstaller from the project root,
# this gets run from the project root.
with open('./capa/version.py', 'wb') as f:
# git output will look like:
#
# tags/v1.0.0-0-g3af38dc
# ------- tag
# - commits since
# g------- git hash fragment
version = (subprocess.check_output(["git", "describe", "--always", "--tags", "--long"])
.strip()
.replace("tags/", ""))
f.write("__version__ = '%s'" % version)
a = Analysis(
# when invoking pyinstaller from the project root,
# this gets invoked from the directory of the spec file,
# i.e. ./.github/pyinstaller
['../../capa/main.py'],
pathex=['capa'],
["../../capa/main.py"],
pathex=["capa"],
binaries=None,
datas=[
# when invoking pyinstaller from the project root,
# this gets invoked from the directory of the spec file,
# i.e. ./.github/pyinstaller
('../../rules', 'rules'),
("../../rules", "rules"),
("../../sigs", "sigs"),
("../../cache", "cache"),
# capa.render.default uses tabulate that depends on wcwidth.
# it seems wcwidth uses a json file `version.json`
# and this doesn't get picked up by pyinstaller automatically.
# so we manually embed the wcwidth resources here.
#
# ref: https://stackoverflow.com/a/62278462/87207
(os.path.dirname(wcwidth.__file__), 'wcwidth')
],
hiddenimports=[
# vivisect does manual/runtime importing of its modules,
# so declare the things that could be imported here.
"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",
"vivisect.analysis.elf.elfplt",
"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.mkpointers",
"vivisect.analysis.generic.pointers",
"vivisect.analysis.generic.pointertables",
"vivisect.analysis.generic.relocations",
"vivisect.analysis.generic.strconst",
"vivisect.analysis.generic.switchcase",
"vivisect.analysis.generic.thunks",
"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.ms",
"vivisect.analysis.ms.hotpatch",
"vivisect.analysis.ms.localhints",
"vivisect.analysis.ms.msvc",
"vivisect.analysis.ms.msvcfunc",
"vivisect.analysis.ms.vftables",
"vivisect.analysis.pe",
"vivisect.impapi.posix.amd64",
"vivisect.impapi.posix.i386",
"vivisect.impapi.windows",
"vivisect.impapi.windows.amd64",
"vivisect.impapi.windows.i386",
"vivisect.impapi.winkern.i386",
"vivisect.impapi.winkern.amd64",
"vivisect.parsers.blob",
"vivisect.parsers.elf",
"vivisect.parsers.ihex",
"vivisect.parsers.macho",
"vivisect.parsers.pe",
"vivisect.parsers.utils",
"vivisect.storage",
"vivisect.storage.basicfile",
"vstruct.constants",
"vstruct.constants.ntstatus",
"vstruct.defs",
"vstruct.defs.arm7",
"vstruct.defs.bmp",
"vstruct.defs.dns",
"vstruct.defs.elf",
"vstruct.defs.gif",
"vstruct.defs.ihex",
"vstruct.defs.inet",
"vstruct.defs.java",
"vstruct.defs.kdcom",
"vstruct.defs.macho",
"vstruct.defs.macho.const",
"vstruct.defs.macho.fat",
"vstruct.defs.macho.loader",
"vstruct.defs.macho.stabs",
"vstruct.defs.minidump",
"vstruct.defs.pcap",
"vstruct.defs.pe",
"vstruct.defs.pptp",
"vstruct.defs.rar",
"vstruct.defs.swf",
"vstruct.defs.win32",
"vstruct.defs.windows",
"vstruct.defs.windows.win_5_1_i386",
"vstruct.defs.windows.win_5_1_i386.ntdll",
"vstruct.defs.windows.win_5_1_i386.ntoskrnl",
"vstruct.defs.windows.win_5_1_i386.win32k",
"vstruct.defs.windows.win_5_2_i386",
"vstruct.defs.windows.win_5_2_i386.ntdll",
"vstruct.defs.windows.win_5_2_i386.ntoskrnl",
"vstruct.defs.windows.win_5_2_i386.win32k",
"vstruct.defs.windows.win_6_1_amd64",
"vstruct.defs.windows.win_6_1_amd64.ntdll",
"vstruct.defs.windows.win_6_1_amd64.ntoskrnl",
"vstruct.defs.windows.win_6_1_amd64.win32k",
"vstruct.defs.windows.win_6_1_i386",
"vstruct.defs.windows.win_6_1_i386.ntdll",
"vstruct.defs.windows.win_6_1_i386.ntoskrnl",
"vstruct.defs.windows.win_6_1_i386.win32k",
"vstruct.defs.windows.win_6_1_wow64",
"vstruct.defs.windows.win_6_1_wow64.ntdll",
"vstruct.defs.windows.win_6_2_amd64",
"vstruct.defs.windows.win_6_2_amd64.ntdll",
"vstruct.defs.windows.win_6_2_amd64.ntoskrnl",
"vstruct.defs.windows.win_6_2_amd64.win32k",
"vstruct.defs.windows.win_6_2_i386",
"vstruct.defs.windows.win_6_2_i386.ntdll",
"vstruct.defs.windows.win_6_2_i386.ntoskrnl",
"vstruct.defs.windows.win_6_2_i386.win32k",
"vstruct.defs.windows.win_6_2_wow64",
"vstruct.defs.windows.win_6_2_wow64.ntdll",
"vstruct.defs.windows.win_6_3_amd64",
"vstruct.defs.windows.win_6_3_amd64.ntdll",
"vstruct.defs.windows.win_6_3_amd64.ntoskrnl",
"vstruct.defs.windows.win_6_3_i386",
"vstruct.defs.windows.win_6_3_i386.ntdll",
"vstruct.defs.windows.win_6_3_i386.ntoskrnl",
"vstruct.defs.windows.win_6_3_wow64",
"vstruct.defs.windows.win_6_3_wow64.ntdll",
(os.path.dirname(wcwidth.__file__), "wcwidth"),
],
# when invoking pyinstaller from the project root,
# this gets run from the project root.
hookspath=['.github/pyinstaller/hooks'],
hookspath=[".github/pyinstaller/hooks"],
runtime_hooks=None,
excludes=[
# ignore packages that would otherwise be bundled with the .exe.
# review: build/pyinstaller/xref-pyinstaller.html
# we don't do any GUI stuff, so ignore these modules
"tkinter",
"_tkinter",
@@ -180,35 +44,51 @@ a = Analysis(
# since we don't spawn a notebook, we can safely remove these.
"IPython",
"ipywidgets",
])
# these are pulled in by networkx
# but we don't need to compute the strongly connected components.
"numpy",
"scipy",
"matplotlib",
"pandas",
"pytest",
# deps from viv that we don't use.
# this duplicates the entries in `hook-vivisect`,
# but works better this way.
"vqt",
"vdb.qt",
"envi.qt",
"PyQt5",
"qt5",
"pyqtwebengine",
"pyasn1",
],
)
a.binaries = a.binaries - TOC([
('tcl85.dll', None, None),
('tk85.dll', None, None),
('_tkinter', None, None)])
a.binaries = a.binaries - TOC([("tcl85.dll", None, None), ("tk85.dll", None, None), ("_tkinter", None, None)])
pyz = PYZ(a.pure, a.zipped_data)
exe = EXE(pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
exclude_binaries=False,
name='capa',
icon='logo.ico',
debug=False,
strip=None,
upx=True,
console=True )
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
exclude_binaries=False,
name="capa",
icon="logo.ico",
debug=False,
strip=None,
upx=True,
console=True,
)
# enable the following to debug the contents of the .exe
#
#coll = COLLECT(exe,
# coll = COLLECT(exe,
# a.binaries,
# a.zipfiles,
# a.datas,
# strip=None,
# upx=True,
# name='capa-dat')

View File

@@ -1,77 +1,119 @@
name: build
on:
release:
types: [created, edited]
jobs:
build:
name: PyInstaller for ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
include:
- os: ubuntu-16.04
# use old linux so that the shared library versioning is more portable
artifact_name: capa
asset_name: linux
- os: windows-latest
artifact_name: capa.exe
asset_name: windows
- os: macos-latest
artifact_name: capa
asset_name: macos
steps:
- name: Checkout capa
uses: actions/checkout@v2
with:
submodules: true
- name: Set up Python 2.7
uses: actions/setup-python@v2
with:
python-version: 2.7
- name: Install PyInstaller
# pyinstaller 4 doesn't support Python 2.7
run: pip install 'pyinstaller==3.*'
- name: Install capa
run: pip install -e .
- name: Build standalone executable
run: pyinstaller .github/pyinstaller/pyinstaller.spec
- name: Does it run?
run: dist/capa "tests/data/Practical Malware Analysis Lab 01-01.dll_"
- uses: actions/upload-artifact@v2
with:
name: ${{ matrix.asset_name }}
path: dist/${{ matrix.artifact_name }}
zip:
name: zip ${{ matrix.asset_name }}
runs-on: ubuntu-latest
needs: build
strategy:
matrix:
include:
- asset_name: linux
artifact_name: capa
- asset_name: windows
artifact_name: capa.exe
- asset_name: macos
artifact_name: capa
steps:
- name: Download ${{ matrix.asset_name }}
uses: actions/download-artifact@v2
with:
name: ${{ matrix.asset_name }}
- name: Set executable flag
run: chmod +x ${{ matrix.artifact_name }}
- name: Set zip name
run: echo ::set-env name=zip_name::capa-${GITHUB_REF#refs/tags/}-${{ matrix.asset_name }}.zip
- name: Zip ${{ matrix.artifact_name }} into ${{ env.zip_name }}
run: zip ${{ env.zip_name }} ${{ matrix.artifact_name }}
- name: Upload ${{ env.zip_name }} to GH Release
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN}}
file: ${{ env.zip_name }}
tag: ${{ github.ref }}
name: build
on:
pull_request:
branches: [ master ]
release:
types: [edited, published]
jobs:
build:
name: PyInstaller for ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
# set to false for debugging
fail-fast: true
matrix:
include:
- os: ubuntu-18.04
# use old linux so that the shared library versioning is more portable
artifact_name: capa
asset_name: linux
- os: windows-2019
artifact_name: capa.exe
asset_name: windows
- os: macos-11
# use older macOS for assumed better portability
artifact_name: capa
asset_name: macos
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
uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 # v4.5.0
with:
python-version: 3.8
- if: matrix.os == 'ubuntu-18.04'
run: sudo apt-get install -y libyaml-dev
- name: Upgrade pip, setuptools
run: python -m pip install --upgrade pip setuptools
- name: Install capa with build requirements
run: pip install -e .[build]
- name: Cache the rule set
run: python ./scripts/cache-ruleset.py ./rules/ ./cache/
- name: Build standalone executable
run: pyinstaller --log-level DEBUG .github/pyinstaller/pyinstaller.spec
- name: Does it run (PE)?
run: dist/capa "tests/data/Practical Malware Analysis Lab 01-01.dll_"
- name: Does it run (Shellcode)?
run: dist/capa "tests/data/499c2a85f6e8142c3f48d4251c9c7cd6.raw32"
- name: Does it run (ELF)?
run: dist/capa "tests/data/7351f8a40c5450557b24622417fc478d.elf_"
- 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 }}
runs-on: ${{ matrix.os }}
needs: [build]
strategy:
matrix:
include:
# OSs not already tested above
- os: ubuntu-18.04
artifact_name: capa
asset_name: linux
- os: ubuntu-20.04
artifact_name: capa
asset_name: linux
- os: windows-2022
artifact_name: capa.exe
asset_name: windows
steps:
- name: Download ${{ matrix.asset_name }}
uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2
with:
name: ${{ matrix.asset_name }}
- name: Set executable flag
if: matrix.os != 'windows-2022'
run: chmod +x ${{ matrix.artifact_name }}
- name: Run capa
run: ./${{ matrix.artifact_name }} -h
zip_and_upload:
# upload zipped binaries to Release page
if: github.event_name == 'release'
name: zip and upload ${{ matrix.asset_name }}
runs-on: ubuntu-20.04
needs: [build]
strategy:
matrix:
include:
- asset_name: linux
artifact_name: capa
- asset_name: windows
artifact_name: capa.exe
- asset_name: macos
artifact_name: capa
steps:
- name: Download ${{ matrix.asset_name }}
uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2
with:
name: ${{ matrix.asset_name }}
- name: Set executable flag
run: chmod +x ${{ matrix.artifact_name }}
- name: Set zip name
run: echo "zip_name=capa-${GITHUB_REF#refs/tags/}-${{ matrix.asset_name }}.zip" >> $GITHUB_ENV
- name: Zip ${{ matrix.artifact_name }} into ${{ env.zip_name }}
run: zip ${{ env.zip_name }} ${{ matrix.artifact_name }}
- name: Upload ${{ env.zip_name }} to GH Release
uses: svenstaro/upload-release-action@2728235f7dc9ff598bd86ce3c274b74f802d2208 # v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN}}
file: ${{ env.zip_name }}
tag: ${{ github.ref }}

41
.github/workflows/changelog.yml vendored Normal file
View File

@@ -0,0 +1,41 @@
name: changelog
on:
# We need pull_request_target instead of pull_request because a write
# repository token is needed to add a review to a PR. DO NOT BUILD
# OR RUN UNTRUSTED CODE FROM PRs IN THIS ACTION
pull_request_target:
types: [opened, edited, synchronize]
jobs:
check_changelog:
# no need to check for dependency updates via dependabot
if: github.actor != 'dependabot[bot]' && github.actor != 'dependabot-preview[bot]'
runs-on: ubuntu-20.04
env:
NO_CHANGELOG: '[x] No CHANGELOG update needed'
steps:
- name: Get changed files
id: files
uses: Ana06/get-changed-files@e0c398b7065a8d84700c471b6afc4116d1ba4e96 # v2.2.0
- name: check changelog updated
id: changelog_updated
env:
PR_BODY: ${{ github.event.pull_request.body }}
FILES: ${{ steps.files.outputs.modified }}
run: |
echo $FILES | grep -qF 'CHANGELOG.md' || echo $PR_BODY | grep -qiF "$NO_CHANGELOG"
- name: Reject pull request if no CHANGELOG update
if: ${{ always() && steps.changelog_updated.outcome == 'failure' }}
uses: Ana06/automatic-pull-request-review@0cf4e8a17ba79344ed3fdd7fed6dd0311d08a9d4 # v0.1.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
event: REQUEST_CHANGES
body: "Please add bug fixes, new features, breaking changes and anything else you think is worthwhile mentioning to the `master (unreleased)` section of CHANGELOG.md. If no CHANGELOG update is needed add the following to the PR description: `${{ env.NO_CHANGELOG }}`"
allow_duplicate: false
- name: Dismiss previous review if CHANGELOG update
uses: Ana06/automatic-pull-request-review@0cf4e8a17ba79344ed3fdd7fed6dd0311d08a9d4 # v0.1.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
event: DISMISS
body: "CHANGELOG updated or no update needed, thanks! :smile:"

View File

@@ -1,29 +1,29 @@
# 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
name: publish to pypi
on:
release:
types: [published]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '2.7'
- 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 }}
run: |
python setup.py sdist bdist_wheel
twine upload --skip-existing dist/*
# 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
name: publish to pypi
on:
release:
types: [published]
jobs:
deploy:
runs-on: ubuntu-20.04
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'
- 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 }}
run: |
python setup.py sdist bdist_wheel
twine upload --skip-existing dist/*

72
.github/workflows/scorecard.yml vendored Normal file
View File

@@ -0,0 +1,72 @@
# This workflow uses actions that are not certified by GitHub. They are provided
# by a third-party and are governed by separate terms of service, privacy
# policy, and support documentation.
name: Scorecard supply-chain security
on:
# For Branch-Protection check. Only the default branch is supported. See
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
branch_protection_rule:
# To guarantee Maintained check is occasionally updated. See
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
schedule:
- cron: '43 4 * * 3'
push:
branches: [ "master" ]
# Declare default permissions as read only.
permissions: read-all
jobs:
analysis:
name: Scorecard analysis
runs-on: ubuntu-latest
permissions:
# Needed to upload the results to code-scanning dashboard.
security-events: write
# Needed to publish results and get a badge (see publish_results below).
id-token: write
# Uncomment the permissions below if installing in a private repository.
# contents: read
# actions: read
steps:
- name: "Checkout code"
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0
with:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@99c53751e09b9529366343771cc321ec74e9bd3d # v2.0.6
with:
results_file: results.sarif
results_format: sarif
# (Optional) "write" PAT token. Uncomment the `repo_token` line below if:
# - you want to enable the Branch-Protection check on a *public* repository, or
# - you are installing Scorecard on a *private* repository
# To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat.
# repo_token: ${{ secrets.SCORECARD_TOKEN }}
# Public repositories:
# - Publish results to OpenSSF REST API for easy access by consumers
# - Allows the repository to include the Scorecard badge.
# - See https://github.com/ossf/scorecard-action#publishing-results.
# For private repositories:
# - `publish_results` will always be set to `false`, regardless
# of the value entered here.
publish_results: true
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab.
- name: "Upload artifact"
uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # v3.1.0
with:
name: SARIF file
path: results.sarif
retention-days: 5
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@807578363a7869ca324a79039e6db9c843e0e100 # v2.1.27
with:
sarif_file: results.sarif

30
.github/workflows/tag.yml vendored Normal file
View File

@@ -0,0 +1,30 @@
name: tag
on:
release:
types: [published]
jobs:
tag:
name: Tag capa rules
runs-on: ubuntu-20.04
steps:
- name: Checkout capa-rules
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
with:
repository: mandiant/capa-rules
token: ${{ secrets.CAPA_TOKEN }}
- name: Tag capa-rules
run: |
# user information is needed to create annotated tags (with a message)
git config user.email 'capa-dev@mandiant.com'
git config user.name 'Capa Bot'
name=${{ github.event.release.tag_name }}
git tag $name -m "https://github.com/mandiant/capa/releases/$name"
# TODO update branch name-major=${name%%.*}
- name: Push tag to capa-rules
uses: ad-m/github-push-action@0fafdd62b84042d49ec0cb92d9cac7f7ce4ec79e # master
with:
repository: mandiant/capa-rules
github_token: ${{ secrets.CAPA_TOKEN }}
tags: true

View File

@@ -6,63 +6,87 @@ on:
pull_request:
branches: [ master ]
# save workspaces to speed up testing
env:
CAPA_SAVE_WORKSPACE: "True"
jobs:
code_style:
runs-on: ubuntu-latest
changelog_format:
runs-on: ubuntu-20.04
steps:
- name: Checkout capa
uses: actions/checkout@v2
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
# The sync GH action in capa-rules relies on a single '- *$' in the CHANGELOG file
- name: Ensure CHANGELOG has '- *$'
run: |
number=$(grep '\- *$' CHANGELOG.md | wc -l)
if [ $number != 1 ]; then exit 1; fi
code_style:
runs-on: ubuntu-20.04
steps:
- name: Checkout capa
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
- name: Set up Python 3.8
uses: actions/setup-python@v2
uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 # v4.5.0
with:
python-version: 3.8
python-version: "3.8"
- name: Install dependencies
run: pip install 'isort==5.*' black
run: pip install -e .[dev]
- name: Lint with isort
run: isort --profile black --length-sort --line-width 120 -c .
- name: Lint with black
run: black -l 120 --check .
- name: Lint with pycodestyle
run: pycodestyle --show-source capa/ scripts/ tests/
- name: Check types with mypy
run: mypy --config-file .github/mypy/mypy.ini --check-untyped-defs capa/ scripts/ tests/
rule_linter:
runs-on: ubuntu-latest
runs-on: ubuntu-20.04
steps:
- name: Checkout capa with rules submodule
uses: actions/checkout@v2
- name: Checkout capa with submodules
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
with:
submodules: true
submodules: recursive
- name: Set up Python 3.8
uses: actions/setup-python@v2
uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 # v4.5.0
with:
python-version: 3.8
# We don't need vivisect, so we can install capa using Python3
python-version: "3.8"
- name: Install capa
run: pip install -e .
- name: Run rule linter
run: python scripts/lint.py rules/
tests:
name: Tests in ${{ matrix.python }}
runs-on: ubuntu-latest
name: Tests in ${{ matrix.python-version }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
needs: [code_style, rule_linter]
strategy:
fail-fast: false
matrix:
os: [ubuntu-20.04, windows-2019, macos-11]
# across all operating systems
python-version: ["3.7", "3.11"]
include:
- python: 2.7
- python: 3.6
- python: 3.7
- python: 3.8
- python: '3.9.0-rc.1' # Python latest
# on Ubuntu run these as well
- os: ubuntu-20.04
python-version: "3.8"
- os: ubuntu-20.04
python-version: "3.9"
steps:
- name: Checkout capa with submodules
uses: actions/checkout@v2
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
with:
submodules: true
- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v2
submodules: recursive
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 # v4.5.0
with:
python-version: ${{ matrix.python }}
python-version: ${{ matrix.python-version }}
- name: Install pyyaml
if: matrix.os == 'ubuntu-20.04'
run: sudo apt-get install -y libyaml-dev
- name: Install capa
run: pip install -e .[dev]
- name: Run tests
run: pytest tests/
run: pytest -v tests/

8
.gitignore vendored
View File

@@ -114,3 +114,11 @@ venv.bak/
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

File diff suppressed because it is too large Load Diff

View File

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

105
README.md
View File

@@ -1,14 +1,21 @@
![capa](.github/logo.png)
![capa](https://github.com/mandiant/capa/blob/master/.github/logo.png)
[![CI status](https://github.com/fireeye/capa/workflows/CI/badge.svg)](https://github.com/fireeye/capa/actions?query=workflow%3ACI+event%3Apush+branch%3Amaster)
[![Number of rules](https://img.shields.io/badge/rules-345-blue.svg)](https://github.com/fireeye/capa-rules)
[![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-770-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 file or shellcode and it tells you what it thinks the program can do.
You run it against a PE, ELF, .NET module, or shellcode file and it tells you what it thinks the program can do.
For example, it might suggest that the file is a backdoor, is capable of installing services, or relies on HTTP to communicate.
Check out the overview in our first [capa blog post](https://www.fireeye.com/blog/threat-research/2020/07/capa-automatically-identify-malware-capabilities.html).
Check out:
- the overview in our first [capa blog post](https://www.mandiant.com/resources/capa-automatically-identify-malware-capabilities)
- the major version 2.0 updates described in our [second blog post](https://www.mandiant.com/resources/capa-2-better-stronger-faster)
- the major version 3.0 (ELF support) described in the [third blog post](https://www.mandiant.com/resources/elfant-in-the-room-capa-v3)
- the major version 4.0 (.NET support) described in the [fourth blog post](https://www.mandiant.com/resources/blog/capa-v4-casting-wider-net)
```
$ capa.exe suspicious.exe
@@ -60,18 +67,11 @@ $ capa.exe suspicious.exe
# download and usage
Download stable releases of the standalone capa binaries [here](https://github.com/fireeye/capa/releases). You can run the standalone binaries without installation. capa is a command line tool that should be run from the terminal.
Download stable releases of the standalone capa binaries [here](https://github.com/mandiant/capa/releases). You can run the standalone binaries without installation. capa is a command line tool that should be run from the terminal.
<!--
Alternatively, you can fetch a nightly build of a standalone binary from one of the following links. These are built using the latest development branch.
- Windows 64bit: TODO
- Linux: TODO
- OSX: TODO
-->
To use capa as a library or integrate with another tool, see [doc/installation.md](https://github.com/mandiant/capa/blob/master/doc/installation.md) for further setup instructions.
To use capa as a library or integrate with another tool, see [doc/installation.md](doc/installation.md) for further setup instructions.
For more information about how to use capa, see [doc/usage.md](doc/usage.md).
For more information about how to use capa, see [doc/usage.md](https://github.com/mandiant/capa/blob/master/doc/usage.md).
# example
@@ -88,31 +88,40 @@ This is useful for at least two reasons:
- it shows where within the binary an experienced analyst might study with IDA Pro
```
λ capa.exe suspicious.exe -vv
$ capa.exe suspicious.exe -vv
...
execute shell command and capture output
namespace c2/shell
author matthew.williams@fireeye.com
author matthew.williams@mandiant.com
scope function
att&ck Execution::Command and Scripting Interpreter::Windows Command Shell [T1059.003]
references https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfoa
examples Practical Malware Analysis Lab 14-02.exe_:0x4011C0
function @ 0x10003A13
function @ 0x4011C0
and:
match: create a process with modified I/O handles and window @ 0x10003A13
match: create a process with modified I/O handles and window @ 0x4011C0
and:
number: 257 = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW @ 0x4012B8
or:
api: kernel32.CreateProcess @ 0x10003D6D
number: 0x101 @ 0x10003B03
or:
number: 0x44 @ 0x10003ADC
optional:
api: kernel32.GetStartupInfo @ 0x10003AE4
match: create pipe @ 0x10003A13
number: 68 = StartupInfo.cb (size) @ 0x401282
or: = API functions that accept a pointer to a STARTUPINFO structure
api: kernel32.CreateProcess @ 0x401343
match: create pipe @ 0x4011C0
or:
api: kernel32.CreatePipe @ 0x10003ACB
api: kernel32.CreatePipe @ 0x40126F, 0x401280
optional:
match: create thread @ 0x40136A, 0x4013BA
or:
and:
os: windows
or:
api: kernel32.CreateThread @ 0x4013D7
or:
and:
os: windows
or:
api: kernel32.CreateThread @ 0x401395
or:
string: cmd.exe /c @ 0x10003AED
string: "cmd.exe" @ 0x4012FD
...
```
@@ -128,37 +137,49 @@ rule:
meta:
name: hash data with CRC32
namespace: data-manipulation/checksum/crc32
author: moritz.raabe@fireeye.com
authors:
- moritz.raabe@mandiant.com
scope: function
mbc:
- Data::Checksum::CRC32 [C0032.001]
examples:
- 2D3EDC218A90F03089CC01715A9F047F:0x403CBD
- 7D28CB106CB54876B2A5C111724A07CD:0x402350 # RtlComputeCrc32
- 7EFF498DE13CC734262F87E6B3EF38AB:0x100084A6
features:
- or:
- and:
- mnemonic: shr
- number: 0xEDB88320
- 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
```
The [github.com/fireeye/capa-rules](https://github.com/fireeye/capa-rules) repository contains hundreds of standard library rules that are distributed with capa.
The [github.com/mandiant/capa-rules](https://github.com/mandiant/capa-rules) repository contains hundreds of standard library rules that are distributed with capa.
Please learn to write rules and contribute new entries as you find interesting techniques in malware.
If you use IDA Pro, then you use can use the [capa explorer IDA plugin](capa/ida/plugin/).
capa explorer lets you quickly identify and navigate to interesting areas of a program and dissect capa rule matches at
the assembly level.
If you use IDA Pro, then you can use the [capa explorer](https://github.com/mandiant/capa/tree/master/capa/ida/plugin) plugin.
capa explorer helps you identify interesting areas of a program and build new capa rules using features extracted directly from your IDA Pro database.
![capa + IDA Pro integration](doc/img/ida_plugin_intro.gif)
![capa + IDA Pro integration](https://github.com/mandiant/capa/blob/master/doc/img/explorer_expanded.png)
# further information
## capa
- [doc/installation](doc/installation.md)
- [doc/usage](doc/usage.md)
- [doc/limitations](doc/limitations.md)
- [Contributing Guide](.github/CONTRIBUTING.md)
- [Installation](https://github.com/mandiant/capa/blob/master/doc/installation.md)
- [Usage](https://github.com/mandiant/capa/blob/master/doc/usage.md)
- [Limitations](https://github.com/mandiant/capa/blob/master/doc/limitations.md)
- [Contributing Guide](https://github.com/mandiant/capa/blob/master/.github/CONTRIBUTING.md)
## capa rules
- [capa-rules repository](https://github.com/fireeye/capa-rules)
- [capa-rules rule format](https://github.com/fireeye/capa-rules/blob/master/doc/format.md)
- [capa-rules repository](https://github.com/mandiant/capa-rules)
- [capa-rules rule format](https://github.com/mandiant/capa-rules/blob/master/doc/format.md)
## capa testfiles
The [capa-testfiles repository](https://github.com/mandiant/capa-testfiles) contains the data we use to test capa's code and rules

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
@@ -6,14 +6,31 @@
# 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 copy
import collections
from typing import TYPE_CHECKING, Set, Dict, List, Tuple, Union, Mapping, Iterable, Iterator, cast
import capa.features
import capa.perf
import capa.features.common
from capa.features.common import Result, Feature
from capa.features.address import Address
if TYPE_CHECKING:
# circular import, otherwise
import capa.rules
class Statement(object):
# a collection of features and the locations at which they are found.
#
# used throughout matching as the context in which features are searched:
# to check if a feature exists, do: `Number(0x10) in features`.
# to collect the locations of a feature, do: `features[Number(0x10)]`
#
# aliased here so that the type can be documented and xref'd.
FeatureSet = Dict[Feature, Set[Address]]
class Statement:
"""
superclass for structural nodes, such as and/or/not.
this exists to provide a default impl for `__str__` and `__repr__`,
@@ -21,7 +38,7 @@ class Statement(object):
"""
def __init__(self, description=None):
super(Statement, self).__init__()
super().__init__()
self.name = self.__class__.__name__
self.description = description
@@ -34,144 +51,179 @@ class Statement(object):
def __repr__(self):
return str(self)
def evaluate(self, ctx):
def evaluate(self, features: FeatureSet, short_circuit=True) -> Result:
"""
classes that inherit `Statement` must implement `evaluate`
args:
ctx (defaultdict[Feature, set[VA]])
returns:
Result
short_circuit (bool): if true, then statements like and/or/some may short circuit.
"""
raise NotImplementedError()
def get_children(self):
def get_children(self) -> Iterator[Union["Statement", Feature]]:
if hasattr(self, "child"):
yield self.child
# this really confuses mypy because the property may not exist
# since its defined in the subclasses.
child = self.child # type: ignore
assert isinstance(child, (Statement, Feature))
yield child
if hasattr(self, "children"):
for child in self.children:
for child in getattr(self, "children"):
assert isinstance(child, (Statement, Feature))
yield child
def replace_child(self, existing, new):
if hasattr(self, "child"):
if self.child is existing:
# this really confuses mypy because the property may not exist
# since its defined in the subclasses.
if self.child is existing: # type: ignore
self.child = new
if hasattr(self, "children"):
for i, child in enumerate(self.children):
children = getattr(self, "children")
for i, child in enumerate(children):
if child is existing:
self.children[i] = new
class Result(object):
"""
represents the results of an evaluation of statements against features.
instances of this class should behave like a bool,
e.g. `assert Result(True, ...) == True`
instances track additional metadata about evaluation results.
they contain references to the statement node (e.g. an And statement),
as well as the children Result instances.
we need this so that we can render the tree of expressions and their results.
"""
def __init__(self, success, statement, children, locations=None):
"""
args:
success (bool)
statement (capa.engine.Statement or capa.features.Feature)
children (list[Result])
locations (iterable[VA])
"""
super(Result, self).__init__()
self.success = success
self.statement = statement
self.children = children
self.locations = locations if locations is not None else ()
def __eq__(self, other):
if isinstance(other, bool):
return self.success == other
return False
def __bool__(self):
return self.success
def __nonzero__(self):
return self.success
children[i] = new
class And(Statement):
"""match if all of the children evaluate to True."""
"""
match if all of the children evaluate to True.
the order of evaluation is dictated by the property
`And.children` (type: List[Statement|Feature]).
a query optimizer may safely manipulate the order of these children.
"""
def __init__(self, children, description=None):
super(And, self).__init__(description=description)
super().__init__(description=description)
self.children = children
def evaluate(self, ctx):
results = [child.evaluate(ctx) for child in self.children]
success = all(results)
return Result(success, self, results)
def evaluate(self, ctx, short_circuit=True):
capa.perf.counters["evaluate.feature"] += 1
capa.perf.counters["evaluate.feature.and"] += 1
if short_circuit:
results = []
for child in self.children:
result = child.evaluate(ctx, short_circuit=short_circuit)
results.append(result)
if not result:
# short circuit
return Result(False, self, results)
return Result(True, self, results)
else:
results = [child.evaluate(ctx, short_circuit=short_circuit) for child in self.children]
success = all(results)
return Result(success, self, results)
class Or(Statement):
"""match if any of the children evaluate to True."""
"""
match if any of the children evaluate to True.
the order of evaluation is dictated by the property
`Or.children` (type: List[Statement|Feature]).
a query optimizer may safely manipulate the order of these children.
"""
def __init__(self, children, description=None):
super(Or, self).__init__(description=description)
super().__init__(description=description)
self.children = children
def evaluate(self, ctx):
results = [child.evaluate(ctx) for child in self.children]
success = any(results)
return Result(success, self, results)
def evaluate(self, ctx, short_circuit=True):
capa.perf.counters["evaluate.feature"] += 1
capa.perf.counters["evaluate.feature.or"] += 1
if short_circuit:
results = []
for child in self.children:
result = child.evaluate(ctx, short_circuit=short_circuit)
results.append(result)
if result:
# short circuit as soon as we hit one match
return Result(True, self, results)
return Result(False, self, results)
else:
results = [child.evaluate(ctx, short_circuit=short_circuit) for child in self.children]
success = any(results)
return Result(success, self, results)
class Not(Statement):
"""match only if the child evaluates to False."""
def __init__(self, child, description=None):
super(Not, self).__init__(description=description)
super().__init__(description=description)
self.child = child
def evaluate(self, ctx):
results = [self.child.evaluate(ctx)]
def evaluate(self, ctx, short_circuit=True):
capa.perf.counters["evaluate.feature"] += 1
capa.perf.counters["evaluate.feature.not"] += 1
results = [self.child.evaluate(ctx, short_circuit=short_circuit)]
success = not results[0]
return Result(success, self, results)
class Some(Statement):
"""match if at least N of the children evaluate to True."""
"""
match if at least N of the children evaluate to True.
the order of evaluation is dictated by the property
`Some.children` (type: List[Statement|Feature]).
a query optimizer may safely manipulate the order of these children.
"""
def __init__(self, count, children, description=None):
super(Some, self).__init__(description=description)
super().__init__(description=description)
self.count = count
self.children = children
def evaluate(self, ctx):
results = [child.evaluate(ctx) for child in self.children]
# note that here we cast the child result as a bool
# because we've overridden `__bool__` above.
#
# we can't use `if child is True` because the instance is not True.
success = sum([1 for child in results if bool(child) is True]) >= self.count
return Result(success, self, results)
def evaluate(self, ctx, short_circuit=True):
capa.perf.counters["evaluate.feature"] += 1
capa.perf.counters["evaluate.feature.some"] += 1
if short_circuit:
results = []
satisfied_children_count = 0
for child in self.children:
result = child.evaluate(ctx, short_circuit=short_circuit)
results.append(result)
if result:
satisfied_children_count += 1
if satisfied_children_count >= self.count:
# short circuit as soon as we hit the threshold
return Result(True, self, results)
return Result(False, self, results)
else:
results = [child.evaluate(ctx, short_circuit=short_circuit) for child in self.children]
# note that here we cast the child result as a bool
# because we've overridden `__bool__` above.
#
# we can't use `if child is True` because the instance is not True.
success = sum([1 for child in results if bool(child) is True]) >= self.count
return Result(success, self, results)
class Range(Statement):
"""match if the child is contained in the ctx set with a count in the given range."""
def __init__(self, child, min=None, max=None, description=None):
super(Range, self).__init__(description=description)
super().__init__(description=description)
self.child = child
self.min = min if min is not None else 0
self.max = max if max is not None else (1 << 64 - 1)
def evaluate(self, ctx):
def evaluate(self, ctx, **kwargs):
capa.perf.counters["evaluate.feature"] += 1
capa.perf.counters["evaluate.feature.range"] += 1
count = len(ctx.get(self.child, []))
if self.min == 0 and count == 0:
return Result(True, self, [])
@@ -191,59 +243,66 @@ class Subscope(Statement):
the engine should preprocess rules to extract subscope statements into their own rules.
"""
def __init__(self, scope, child):
super(Subscope, self).__init__()
def __init__(self, scope, child, description=None):
super().__init__(description=description)
self.scope = scope
self.child = child
def evaluate(self, ctx):
def evaluate(self, ctx, **kwargs):
raise ValueError("cannot evaluate a subscope directly!")
def topologically_order_rules(rules):
# mapping from rule name to list of: (location of match, result object)
#
# used throughout matching and rendering to collection the results
# of statement evaluation and their locations.
#
# to check if a rule matched, do: `"TCP client" in matches`.
# to find where a rule matched, do: `map(first, matches["TCP client"])`
# to see how a rule matched, do:
#
# for address, match_details in matches["TCP client"]:
# inspect(match_details)
#
# aliased here so that the type can be documented and xref'd.
MatchResults = Mapping[str, List[Tuple[Address, Result]]]
def index_rule_matches(features: FeatureSet, rule: "capa.rules.Rule", locations: Iterable[Address]):
"""
order the given rules such that dependencies show up before dependents.
this means that as we match rules, we can add features for the matches, and these
will be matched by subsequent rules if they follow this order.
record into the given featureset that the given rule matched at the given locations.
assumes that the rule dependency graph is a DAG.
naively, this is just adding a MatchedRule feature;
however, we also want to record matches for the rule's namespaces.
updates `features` in-place. doesn't modify the remaining arguments.
"""
# we evaluate `rules` multiple times, so if its a generator, realize it into a list.
rules = list(rules)
namespaces = capa.rules.index_rules_by_namespace(rules)
rules = {rule.name: rule for rule in rules}
seen = set([])
ret = []
def rec(rule):
if rule.name in seen:
return
for dep in rule.get_dependencies(namespaces):
rec(rules[dep])
ret.append(rule)
seen.add(rule.name)
for rule in rules.values():
rec(rule)
return ret
features[capa.features.common.MatchedRule(rule.name)].update(locations)
namespace = rule.meta.get("namespace")
if namespace:
while namespace:
features[capa.features.common.MatchedRule(namespace)].update(locations)
namespace, _, _ = namespace.rpartition("/")
def match(rules, features, va):
def match(rules: List["capa.rules.Rule"], features: FeatureSet, addr: Address) -> Tuple[FeatureSet, MatchResults]:
"""
Args:
rules (List[capa.rules.Rule]): these must already be ordered topologically by dependency.
features (Mapping[capa.features.Feature, int]):
va (int): location of the features
match the given rules against the given features,
returning an updated set of features and the matches.
Returns:
Tuple[List[capa.features.Feature], Dict[str, Tuple[int, capa.engine.Result]]]: two-tuple with entries:
- list of features used for matching (which may be greater than argument, due to rule match features), and
- mapping from rule name to (location of match, result object)
the updated features are just like the input,
but extended to include the match features (e.g. names of rules that matched).
the given feature set is not modified; an updated copy is returned.
the given list of rules must be ordered topologically by dependency,
or else `match` statements will not be handled correctly.
this routine should be fairly optimized, but is not guaranteed to be the fastest matcher possible.
it has a particularly convenient signature: (rules, features) -> matches
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)
results = collections.defaultdict(list) # type: MatchResults
# copy features so that we can modify it
# without affecting the caller (keep this function pure)
@@ -252,15 +311,22 @@ def match(rules, features, va):
features = collections.defaultdict(set, copy.copy(features))
for rule in rules:
res = rule.evaluate(features)
res = rule.evaluate(features, short_circuit=True)
if res:
results[rule.name].append((va, res))
features[capa.features.MatchedRule(rule.name)].add(va)
# we first matched the rule with short circuiting enabled.
# this is much faster than without short circuiting.
# however, we want to collect all results thoroughly,
# so once we've found a match quickly,
# go back and capture results without short circuiting.
res = rule.evaluate(features, short_circuit=False)
namespace = rule.meta.get("namespace")
if namespace:
while namespace:
features[capa.features.MatchedRule(namespace)].add(va)
namespace, _, _ = namespace.rpartition("/")
# sanity check
assert bool(res) is True
results[rule.name].append((addr, res))
# we need to update the current `features`
# because subsequent iterations of this loop may use newly added features,
# such as rule or namespace matches.
index_rule_matches(features, rule, [addr])
return (features, results)

14
capa/exceptions.py Normal file
View File

@@ -0,0 +1,14 @@
class UnsupportedRuntimeError(RuntimeError):
pass
class UnsupportedFormatError(ValueError):
pass
class UnsupportedArchError(ValueError):
pass
class UnsupportedOSError(ValueError):
pass

View File

@@ -1,219 +0,0 @@
# Copyright (C) 2020 FireEye, 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 sys
import codecs
import logging
import capa.engine
logger = logging.getLogger(__name__)
MAX_BYTES_FEATURE_SIZE = 0x100
# identifiers for supported architectures names that tweak a feature
# for example, offset/x32
ARCH_X32 = "x32"
ARCH_X64 = "x64"
VALID_ARCH = (ARCH_X32, ARCH_X64)
def bytes_to_str(b):
if sys.version_info[0] >= 3:
return str(codecs.encode(b, "hex").decode("utf-8"))
else:
return codecs.encode(b, "hex")
def hex_string(h):
""" render hex string e.g. "0a40b1" as "0A 40 B1" """
return " ".join(h[i : i + 2] for i in range(0, len(h), 2)).upper()
class Feature(object):
def __init__(self, value, arch=None, description=None):
"""
Args:
value (any): the value of the feature, such as the number or string.
arch (str): one of the VALID_ARCH values, or None.
When None, then the feature applies to any architecture.
Modifies the feature name from `feature` to `feature/arch`, like `offset/x32`.
description (str): a human-readable description that explains the feature value.
"""
super(Feature, self).__init__()
if arch is not None:
if arch not in VALID_ARCH:
raise ValueError("arch '%s' must be one of %s" % (arch, VALID_ARCH))
self.name = self.__class__.__name__.lower() + "/" + arch
else:
self.name = self.__class__.__name__.lower()
self.value = value
self.arch = arch
self.description = description
def __hash__(self):
return hash((self.name, self.value, self.arch))
def __eq__(self, other):
return self.name == other.name and self.value == other.value and self.arch == other.arch
def get_value_str(self):
"""
render the value of this feature, for use by `__str__` and friends.
subclasses should override to customize the rendering.
Returns: any
"""
return self.value
def __str__(self):
if self.value:
if self.description:
return "%s(%s = %s)" % (self.name, self.get_value_str(), self.description)
else:
return "%s(%s)" % (self.name, self.get_value_str())
else:
return "%s" % self.name
def __repr__(self):
return str(self)
def evaluate(self, ctx):
return capa.engine.Result(self in ctx, self, [], locations=ctx.get(self, []))
def freeze_serialize(self):
if self.arch is not None:
return (self.__class__.__name__, [self.value, {"arch": self.arch}])
else:
return (self.__class__.__name__, [self.value])
@classmethod
def freeze_deserialize(cls, args):
# as you can see below in code,
# if the last argument is a dictionary,
# consider it to be kwargs passed to the feature constructor.
if len(args) == 1:
return cls(*args)
elif isinstance(args[-1], dict):
kwargs = args[-1]
args = args[:-1]
return cls(*args, **kwargs)
class MatchedRule(Feature):
def __init__(self, value, description=None):
super(MatchedRule, self).__init__(value, description=description)
self.name = "match"
class Characteristic(Feature):
def __init__(self, value, description=None):
super(Characteristic, self).__init__(value, description=description)
class String(Feature):
def __init__(self, value, description=None):
super(String, self).__init__(value, description=description)
class Regex(String):
def __init__(self, value, description=None):
super(Regex, self).__init__(value, description=description)
pat = self.value[len("/") : -len("/")]
flags = re.DOTALL
if value.endswith("/i"):
pat = self.value[len("/") : -len("/i")]
flags |= re.IGNORECASE
try:
self.re = re.compile(pat, flags)
except re.error:
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
)
def evaluate(self, ctx):
for feature, locations in ctx.items():
if not isinstance(feature, (capa.features.String,)):
continue
# `re.search` finds a match anywhere in the given string
# which implies leading and/or trailing whitespace.
# using this mode cleans is more convenient for rule authors,
# so that they don't have to prefix/suffix their terms like: /.*foo.*/.
if self.re.search(feature.value):
# unlike other features, we cannot return put a reference to `self` directly in a `Result`.
# this is because `self` may match on many strings, so we can't stuff the matched value into it.
# instead, return a new instance that has a reference to both the regex and the matched value.
# see #262.
return capa.engine.Result(True, _MatchedRegex(self, feature.value), [], locations=locations)
return capa.engine.Result(False, _MatchedRegex(self, None), [])
def __str__(self):
return "regex(string =~ %s)" % self.value
class _MatchedRegex(Regex):
"""
this represents a specific instance of a regular expression feature match.
treat it the same as a `Regex` except it has the `match` field that contains the complete string that matched.
note: this type should only ever be constructed by `Regex.evaluate()`. it is not part of the public API.
"""
def __init__(self, regex, match):
"""
args:
regex (Regex): the regex feature that matches
match (string|None): the matching string or None if it doesn't match
"""
super(_MatchedRegex, self).__init__(regex.value, description=regex.description)
# we want this to collide with the name of `Regex` above,
# so that it works nicely with the renderers.
self.name = "regex"
# this may be None if the regex doesn't match
self.match = match
def __str__(self):
return 'regex(string =~ %s, matched = "%s")' % (self.value, self.match)
class StringFactory(object):
def __new__(self, value, description=None):
if value.startswith("/") and (value.endswith("/") or value.endswith("/i")):
return Regex(value, description=description)
return String(value, description=description)
class Bytes(Feature):
def __init__(self, value, description=None):
super(Bytes, self).__init__(value, description=description)
def evaluate(self, ctx):
for feature, locations in ctx.items():
if not isinstance(feature, (capa.features.Bytes,)):
continue
if feature.value.startswith(self.value):
return capa.engine.Result(True, self, [], locations=locations)
return capa.engine.Result(False, self, [])
def get_value_str(self):
return hex_string(bytes_to_str(self.value))
def freeze_serialize(self):
return (self.__class__.__name__, [bytes_to_str(self.value).upper()])
@classmethod
def freeze_deserialize(cls, args):
return cls(*[codecs.decode(x, "hex") for x in args])

114
capa/features/address.py Normal file
View File

@@ -0,0 +1,114 @@
import abc
class Address(abc.ABC):
@abc.abstractmethod
def __eq__(self, other):
...
@abc.abstractmethod
def __lt__(self, other):
# implement < so that addresses can be sorted from low to high
...
@abc.abstractmethod
def __hash__(self):
# implement hash so that addresses can be used in sets and dicts
...
@abc.abstractmethod
def __repr__(self):
# implement repr to help during debugging
...
class AbsoluteVirtualAddress(int, Address):
"""an absolute memory address"""
def __new__(cls, v):
assert v >= 0
return int.__new__(cls, v)
def __repr__(self):
return f"absolute(0x{self:x})"
def __hash__(self):
return int.__hash__(self)
class RelativeVirtualAddress(int, Address):
"""a memory address relative to a base address"""
def __repr__(self):
return f"relative(0x{self:x})"
def __hash__(self):
return int.__hash__(self)
class FileOffsetAddress(int, Address):
"""an address relative to the start of a file"""
def __new__(cls, v):
assert v >= 0
return int.__new__(cls, v)
def __repr__(self):
return f"file(0x{self:x})"
def __hash__(self):
return int.__hash__(self)
class DNTokenAddress(int, Address):
"""a .NET token"""
def __new__(cls, token: int):
return int.__new__(cls, token)
def __repr__(self):
return f"token(0x{self:x})"
def __hash__(self):
return int.__hash__(self)
class DNTokenOffsetAddress(Address):
"""an offset into an object specified by a .NET token"""
def __init__(self, token: int, offset: int):
assert offset >= 0
self.token = token
self.offset = offset
def __eq__(self, other):
return (self.token, self.offset) == (other.token, other.offset)
def __lt__(self, other):
return (self.token, self.offset) < (other.token, other.offset)
def __hash__(self):
return hash((self.token, self.offset))
def __repr__(self):
return f"token(0x{self.token:x})+(0x{self.offset:x})"
def __index__(self):
return self.token + self.offset
class _NoAddress(Address):
def __eq__(self, other):
return True
def __lt__(self, other):
return False
def __hash__(self):
return hash(0)
def __repr__(self):
return "no address"
NO_ADDRESS = _NoAddress()

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
@@ -6,22 +6,15 @@
# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and limitations under the License.
from capa.features import Feature
from capa.features.common import Feature
class BasicBlock(Feature):
def __init__(self):
super(BasicBlock, self).__init__(None)
def __init__(self, description=None):
super().__init__(0, description=description)
def __str__(self):
return "basic block"
def get_value_str(self):
return ""
def freeze_serialize(self):
return (self.__class__.__name__, [])
@classmethod
def freeze_deserialize(cls, args):
return cls()

455
capa/features/common.py Normal file
View File

@@ -0,0 +1,455 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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 abc
import codecs
import typing
import logging
import collections
from typing import TYPE_CHECKING, Set, Dict, List, Union, Optional
if TYPE_CHECKING:
# circular import, otherwise
import capa.engine
import capa.perf
import capa.features
import capa.features.extractors.elf
from capa.features.address import Address
logger = logging.getLogger(__name__)
MAX_BYTES_FEATURE_SIZE = 0x100
# thunks may be chained so we specify a delta to control the depth to which these chains are explored
THUNK_CHAIN_DEPTH_DELTA = 5
class FeatureAccess:
READ = "read"
WRITE = "write"
VALID_FEATURE_ACCESS = (FeatureAccess.READ, FeatureAccess.WRITE)
def bytes_to_str(b: bytes) -> str:
return str(codecs.encode(b, "hex").decode("utf-8"))
def hex_string(h: str) -> str:
"""render hex string e.g. "0a40b1" as "0A 40 B1" """
return " ".join(h[i : i + 2] for i in range(0, len(h), 2)).upper()
def escape_string(s: str) -> str:
"""escape special characters"""
s = repr(s)
if not s.startswith(('"', "'")):
# u'hello\r\nworld' -> hello\\r\\nworld
s = s[2:-1]
else:
# 'hello\r\nworld' -> hello\\r\\nworld
s = s[1:-1]
s = s.replace("\\'", "'") # repr() may escape "'" in some edge cases, remove
s = s.replace('"', '\\"') # repr() does not escape '"', add
return s
class Result:
"""
represents the results of an evaluation of statements against features.
instances of this class should behave like a bool,
e.g. `assert Result(True, ...) == True`
instances track additional metadata about evaluation results.
they contain references to the statement node (e.g. an And statement),
as well as the children Result instances.
we need this so that we can render the tree of expressions and their results.
"""
def __init__(
self,
success: bool,
statement: Union["capa.engine.Statement", "Feature"],
children: List["Result"],
locations: Optional[Set[Address]] = None,
):
super().__init__()
self.success = success
self.statement = statement
self.children = children
self.locations = locations if locations is not None else set()
def __eq__(self, other):
if isinstance(other, bool):
return self.success == other
return False
def __bool__(self):
return self.success
def __nonzero__(self):
return self.success
class Feature(abc.ABC):
def __init__(
self,
value: Union[str, int, float, bytes],
description: Optional[str] = None,
):
"""
Args:
value (any): the value of the feature, such as the number or string.
description (str): a human-readable description that explains the feature value.
"""
super().__init__()
self.name = self.__class__.__name__.lower()
self.value = value
self.description = description
def __hash__(self):
return hash((self.name, self.value))
def __eq__(self, other):
return self.name == other.name and self.value == other.value
def __lt__(self, other):
# TODO: this is 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()
)
def get_name_str(self) -> str:
"""
render the name of this feature, for use by `__str__` and friends.
subclasses should override to customize the rendering.
"""
return self.name
def get_value_str(self) -> str:
"""
render the value of this feature, for use by `__str__` and friends.
subclasses should override to customize the rendering.
"""
return str(self.value)
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)
else:
return "%s(%s)" % (self.get_name_str(), self.get_value_str())
else:
return "%s" % self.get_name_str()
def __repr__(self):
return str(self)
def evaluate(self, ctx: Dict["Feature", Set[Address]], **kwargs) -> Result:
capa.perf.counters["evaluate.feature"] += 1
capa.perf.counters["evaluate.feature." + self.name] += 1
return Result(self in ctx, self, [], locations=ctx.get(self, set()))
class MatchedRule(Feature):
def __init__(self, value: str, description=None):
super().__init__(value, description=description)
self.name = "match"
class Characteristic(Feature):
def __init__(self, value: str, description=None):
super().__init__(value, description=description)
class String(Feature):
def __init__(self, value: str, description=None):
super().__init__(value, description=description)
def get_value_str(self) -> str:
assert isinstance(self.value, str)
return escape_string(self.value)
class Class(Feature):
def __init__(self, value: str, description=None):
super().__init__(value, description=description)
class Namespace(Feature):
def __init__(self, value: str, description=None):
super().__init__(value, description=description)
class Substring(String):
def __init__(self, value: str, description=None):
super().__init__(value, description=description)
self.value = value
def evaluate(self, ctx, short_circuit=True):
capa.perf.counters["evaluate.feature"] += 1
capa.perf.counters["evaluate.feature.substring"] += 1
# mapping from string value to list of locations.
# will unique the locations later on.
matches: typing.DefaultDict[str, Set[Address]] = collections.defaultdict(set)
assert isinstance(self.value, str)
for feature, locations in ctx.items():
if not isinstance(feature, (String,)):
continue
if not isinstance(feature.value, str):
# this is a programming error: String should only contain str
raise ValueError("unexpected feature value type")
if self.value in feature.value:
matches[feature.value].update(locations)
if short_circuit:
# we found one matching string, thats sufficient to match.
# don't collect other matching strings in this mode.
break
if matches:
# collect all locations
locations = set()
for locs in matches.values():
locations.update(locs)
# unlike other features, we cannot return put a reference to `self` directly in a `Result`.
# this is because `self` may match on many strings, so we can't stuff the matched value into it.
# instead, return a new instance that has a reference to both the substring and the matched values.
return Result(True, _MatchedSubstring(self, dict(matches)), [], locations=locations)
else:
return Result(False, _MatchedSubstring(self, {}), [])
def get_value_str(self) -> str:
assert isinstance(self.value, str)
return escape_string(self.value)
def __str__(self):
assert isinstance(self.value, str)
return "substring(%s)" % escape_string(self.value)
class _MatchedSubstring(Substring):
"""
this represents specific match instances of a substring feature.
treat it the same as a `Substring` except it has the `matches` field that contains the complete strings that matched.
note: this type should only ever be constructed by `Substring.evaluate()`. it is not part of the public API.
"""
def __init__(self, substring: Substring, matches: Dict[str, Set[Address]]):
"""
args:
substring: the substring feature that matches.
match: mapping from matching string to its locations.
"""
super().__init__(str(substring.value), description=substring.description)
# we want this to collide with the name of `Substring` above,
# so that it works nicely with the renderers.
self.name = "substring"
# this may be None if the substring doesn't match
self.matches = matches
def __str__(self):
assert isinstance(self.value, str)
return 'substring("%s", matches = %s)' % (
self.value,
", ".join(map(lambda s: '"' + s + '"', (self.matches or {}).keys())),
)
class Regex(String):
def __init__(self, value: str, description=None):
super().__init__(value, description=description)
self.value = value
pat = self.value[len("/") : -len("/")]
flags = re.DOTALL
if value.endswith("/i"):
pat = self.value[len("/") : -len("/i")]
flags |= re.IGNORECASE
try:
self.re = re.compile(pat, flags)
except re.error as exc:
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
) from exc
def evaluate(self, ctx, short_circuit=True):
capa.perf.counters["evaluate.feature"] += 1
capa.perf.counters["evaluate.feature.regex"] += 1
# mapping from string value to list of locations.
# will unique the locations later on.
matches: typing.DefaultDict[str, Set[Address]] = collections.defaultdict(set)
for feature, locations in ctx.items():
if not isinstance(feature, (String,)):
continue
if not isinstance(feature.value, str):
# this is a programming error: String should only contain str
raise ValueError("unexpected feature value type")
# `re.search` finds a match anywhere in the given string
# which implies leading and/or trailing whitespace.
# using this mode cleans is more convenient for rule authors,
# so that they don't have to prefix/suffix their terms like: /.*foo.*/.
if self.re.search(feature.value):
matches[feature.value].update(locations)
if short_circuit:
# we found one matching string, thats sufficient to match.
# don't collect other matching strings in this mode.
break
if matches:
# collect all locations
locations = set()
for locs in matches.values():
locations.update(locs)
# unlike other features, we cannot return put a reference to `self` directly in a `Result`.
# this is because `self` may match on many strings, so we can't stuff the matched value into it.
# instead, return a new instance that has a reference to both the regex and the matched values.
# see #262.
return Result(True, _MatchedRegex(self, dict(matches)), [], locations=locations)
else:
return Result(False, _MatchedRegex(self, {}), [])
def __str__(self):
assert isinstance(self.value, str)
return "regex(string =~ %s)" % self.value
class _MatchedRegex(Regex):
"""
this represents specific match instances of a regular expression feature.
treat it the same as a `Regex` except it has the `matches` field that contains the complete strings that matched.
note: this type should only ever be constructed by `Regex.evaluate()`. it is not part of the public API.
"""
def __init__(self, regex: Regex, matches: Dict[str, Set[Address]]):
"""
args:
regex: the regex feature that matches.
matches: mapping from matching string to its locations.
"""
super().__init__(str(regex.value), description=regex.description)
# we want this to collide with the name of `Regex` above,
# so that it works nicely with the renderers.
self.name = "regex"
# this may be None if the regex doesn't match
self.matches = matches
def __str__(self):
assert isinstance(self.value, str)
return "regex(string =~ %s, matches = %s)" % (
self.value,
", ".join(map(lambda s: '"' + s + '"', (self.matches or {}).keys())),
)
class StringFactory:
def __new__(cls, value: str, description=None):
if value.startswith("/") and (value.endswith("/") or value.endswith("/i")):
return Regex(value, description=description)
return String(value, description=description)
class Bytes(Feature):
def __init__(self, value: bytes, description=None):
super().__init__(value, description=description)
self.value = value
def evaluate(self, ctx, **kwargs):
capa.perf.counters["evaluate.feature"] += 1
capa.perf.counters["evaluate.feature.bytes"] += 1
assert isinstance(self.value, bytes)
for feature, locations in ctx.items():
if not isinstance(feature, (Bytes,)):
continue
assert isinstance(feature.value, bytes)
if feature.value.startswith(self.value):
return Result(True, self, [], locations=locations)
return Result(False, self, [])
def get_value_str(self):
assert isinstance(self.value, bytes)
return hex_string(bytes_to_str(self.value))
# other candidates here: https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#machine-types
ARCH_I386 = "i386"
ARCH_AMD64 = "amd64"
# dotnet
ARCH_ANY = "any"
VALID_ARCH = (ARCH_I386, ARCH_AMD64, ARCH_ANY)
class Arch(Feature):
def __init__(self, value: str, description=None):
super().__init__(value, description=description)
self.name = "arch"
OS_WINDOWS = "windows"
OS_LINUX = "linux"
OS_MACOS = "macos"
# dotnet
OS_ANY = "any"
VALID_OS = {os.value for os in capa.features.extractors.elf.OS}
VALID_OS.update({OS_WINDOWS, OS_LINUX, OS_MACOS, OS_ANY})
class OS(Feature):
def __init__(self, value: str, description=None):
super().__init__(value, description=description)
self.name = "os"
FORMAT_PE = "pe"
FORMAT_ELF = "elf"
FORMAT_DOTNET = "dotnet"
VALID_FORMAT = (FORMAT_PE, FORMAT_ELF, FORMAT_DOTNET)
# internal only, not to be used in rules
FORMAT_AUTO = "auto"
FORMAT_SC32 = "sc32"
FORMAT_SC64 = "sc64"
FORMAT_FREEZE = "freeze"
FORMAT_UNKNOWN = "unknown"
class Format(Feature):
def __init__(self, value: str, description=None):
super().__init__(value, description=description)
self.name = "format"
def is_global_feature(feature):
"""
is this a feature that is extracted at every scope?
today, these are OS and arch features.
"""
return isinstance(feature, (OS, Arch))

View File

@@ -1,286 +0,0 @@
# Copyright (C) 2020 FireEye, 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
class FeatureExtractor(object):
"""
FeatureExtractor defines the interface for fetching features from a sample.
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
serialized JSON file and do matching without a binary analysis pass.
Also, this provides a way to hook in an IDA backend.
This class is not instantiated directly; it is the base class for other implementations.
"""
__metaclass__ = abc.ABCMeta
def __init__(self):
#
# 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(FeatureExtractor, self).__init__()
@abc.abstractmethod
def get_base_address(self):
"""
fetch the preferred load address at which the sample was analyzed.
returns: int
"""
raise NotImplemented
@abc.abstractmethod
def extract_file_features(self):
"""
extract file-scope features.
example::
extractor = VivisectFeatureExtractor(vw, path)
for feature, va in extractor.get_file_features():
print('0x%x: %s', va, feature)
yields:
Tuple[capa.features.Feature, int]: feature and its location
"""
raise NotImplemented
@abc.abstractmethod
def get_functions(self):
"""
enumerate the functions and provide opaque values that will
subsequently be provided to `.extract_function_features()`, etc.
by "opaque value", we mean that this can be any object, as long as it
provides enough context to `.extract_function_features()`.
the opaque value should support casting to int (`__int__`) for the function start address.
yields:
any: the opaque function value.
"""
raise NotImplemented
@abc.abstractmethod
def extract_function_features(self, f):
"""
extract function-scope features.
the arguments are opaque values previously provided by `.get_functions()`, etc.
example::
extractor = VivisectFeatureExtractor(vw, path)
for function in extractor.get_functions():
for feature, va in extractor.extract_function_features(function):
print('0x%x: %s', va, feature)
args:
f [any]: an opaque value previously fetched from `.get_functions()`.
yields:
Tuple[capa.features.Feature, int]: feature and its location
"""
raise NotImplemented
@abc.abstractmethod
def get_basic_blocks(self, f):
"""
enumerate the basic blocks in the given function and provide opaque values that will
subsequently be provided to `.extract_basic_block_features()`, etc.
by "opaque value", we mean that this can be any object, as long as it
provides enough context to `.extract_basic_block_features()`.
the opaque value should support casting to int (`__int__`) for the basic block start address.
yields:
any: the opaque basic block value.
"""
raise NotImplemented
@abc.abstractmethod
def extract_basic_block_features(self, f, bb):
"""
extract basic block-scope features.
the arguments are opaque values previously provided by `.get_functions()`, etc.
example::
extractor = VivisectFeatureExtractor(vw, path)
for function in extractor.get_functions():
for bb in extractor.get_basic_blocks(function):
for feature, va in extractor.extract_basic_block_features(function, bb):
print('0x%x: %s', va, feature)
args:
f [any]: an opaque value previously fetched from `.get_functions()`.
bb [any]: an opaque value previously fetched from `.get_basic_blocks()`.
yields:
Tuple[capa.features.Feature, int]: feature and its location
"""
raise NotImplemented
@abc.abstractmethod
def get_instructions(self, f, bb):
"""
enumerate the instructions in the given basic block and provide opaque values that will
subsequently be provided to `.extract_insn_features()`, etc.
by "opaque value", we mean that this can be any object, as long as it
provides enough context to `.extract_insn_features()`.
the opaque value should support casting to int (`__int__`) for the instruction address.
yields:
any: the opaque function value.
"""
raise NotImplemented
@abc.abstractmethod
def extract_insn_features(self, f, bb, insn):
"""
extract instruction-scope features.
the arguments are opaque values previously provided by `.get_functions()`, etc.
example::
extractor = VivisectFeatureExtractor(vw, path)
for function in extractor.get_functions():
for bb in extractor.get_basic_blocks(function):
for insn in extractor.get_instructions(function, bb):
for feature, va in extractor.extract_insn_features(function, bb, insn):
print('0x%x: %s', va, feature)
args:
f [any]: an opaque value previously fetched from `.get_functions()`.
bb [any]: an opaque value previously fetched from `.get_basic_blocks()`.
insn [any]: an opaque value previously fetched from `.get_instructions()`.
yields:
Tuple[capa.features.Feature, int]: feature and its location
"""
raise NotImplemented
class NullFeatureExtractor(FeatureExtractor):
"""
An extractor that extracts some user-provided features.
The structure of the single parameter is demonstrated in the example below.
This is useful for testing, as we can provide expected values and see if matching works.
Also, this is how we represent features deserialized from a freeze file.
example::
extractor = NullFeatureExtractor({
'base address: 0x401000,
'file features': [
(0x402345, capa.features.Characteristic('embedded pe')),
],
'functions': {
0x401000: {
'features': [
(0x401000, capa.features.Characteristic('nzxor')),
],
'basic blocks': {
0x401000: {
'features': [
(0x401000, capa.features.Characteristic('tight-loop')),
],
'instructions': {
0x401000: {
'features': [
(0x401000, capa.features.Characteristic('nzxor')),
],
},
0x401002: ...
}
},
0x401005: ...
}
},
0x40200: ...
}
)
"""
def __init__(self, features):
super(NullFeatureExtractor, self).__init__()
self.features = features
def get_base_address(self):
return self.features["base address"]
def extract_file_features(self):
for p in self.features.get("file features", []):
va, feature = p
yield feature, va
def get_functions(self):
for va in sorted(self.features["functions"].keys()):
yield va
def extract_function_features(self, f):
for p in self.features.get("functions", {}).get(f, {}).get("features", []): # noqa: E127 line over-indented
va, feature = p
yield feature, va
def get_basic_blocks(self, f):
for va in sorted(
self.features.get("functions", {}) # noqa: E127 line over-indented
.get(f, {})
.get("basic blocks", {})
.keys()
):
yield va
def extract_basic_block_features(self, f, bb):
for p in (
self.features.get("functions", {}) # noqa: E127 line over-indented
.get(f, {})
.get("basic blocks", {})
.get(bb, {})
.get("features", [])
):
va, feature = p
yield feature, va
def get_instructions(self, f, bb):
for va in sorted(
self.features.get("functions", {}) # noqa: E127 line over-indented
.get(f, {})
.get("basic blocks", {})
.get(bb, {})
.get("instructions", {})
.keys()
):
yield va
def extract_insn_features(self, f, bb, insn):
for p in (
self.features.get("functions", {}) # noqa: E127 line over-indented
.get(f, {})
.get("basic blocks", {})
.get(bb, {})
.get("instructions", {})
.get(insn, {})
.get("features", [])
):
va, feature = p
yield feature, va

View File

@@ -0,0 +1,264 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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
import dataclasses
from typing import Any, Dict, Tuple, Union, Iterator
from dataclasses import dataclass
import capa.features.address
from capa.features.common import Feature
from capa.features.address import Address, 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.
#
# these handles are only consumed by routines on
# the feature extractor from which they were created.
@dataclass
class FunctionHandle:
"""reference to a function recognized by a feature extractor.
Attributes:
address: the address of the function.
inner: extractor-specific data.
ctx: a context object for the extractor.
"""
address: Address
inner: Any
ctx: Dict[str, Any] = dataclasses.field(default_factory=dict)
@dataclass
class BBHandle:
"""reference to a basic block recognized by a feature extractor.
Attributes:
address: the address of the basic block start address.
inner: extractor-specific data.
"""
address: Address
inner: Any
@dataclass
class InsnHandle:
"""reference to a instruction recognized by a feature extractor.
Attributes:
address: the address of the instruction address.
inner: extractor-specific data.
"""
address: Address
inner: Any
class FeatureExtractor:
"""
FeatureExtractor defines the interface for fetching features from a sample.
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
serialized JSON file and do matching without a binary analysis pass.
Also, this provides a way to hook in an IDA backend.
This class is not instantiated directly; it is the base class for other implementations.
"""
__metaclass__ = abc.ABCMeta
def __init__(self):
#
# 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__()
@abc.abstractmethod
def get_base_address(self) -> Union[AbsoluteVirtualAddress, capa.features.address._NoAddress]:
"""
fetch the preferred load address at which the sample was analyzed.
when the base address is `NO_ADDRESS`, then the loader has no concept of a preferred load address.
such as: shellcode, .NET modules, etc.
in these scenarios, RelativeVirtualAddresses aren't used.
"""
raise NotImplementedError()
@abc.abstractmethod
def extract_global_features(self) -> Iterator[Tuple[Feature, Address]]:
"""
extract features found at every scope ("global").
example::
extractor = VivisectFeatureExtractor(vw, path)
for feature, va in extractor.get_global_features():
print('0x%x: %s', va, 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 = VivisectFeatureExtractor(vw, path)
for feature, va in extractor.get_file_features():
print('0x%x: %s', va, feature)
yields:
Tuple[Feature, Address]: feature and its location
"""
raise NotImplementedError()
@abc.abstractmethod
def get_functions(self) -> Iterator[FunctionHandle]:
"""
enumerate the functions and provide opaque values that will
subsequently be provided to `.extract_function_features()`, etc.
"""
raise NotImplementedError()
def is_library_function(self, addr: Address) -> bool:
"""
is the given address a library function?
the backend may implement its own function matching algorithm, or none at all.
we accept an address here, rather than function object,
to handle addresses identified in instructions.
this information is used to:
- filter out matches in library functions (by default), and
- recognize when to fetch symbol names for called (non-API) functions
args:
addr (Address): the address of a function.
returns:
bool: True if the given address is the start of a library function.
"""
return False
def get_function_name(self, addr: Address) -> str:
"""
fetch any recognized name for the given address.
this is only guaranteed to return a value when the given function is a recognized library function.
we accept a VA here, rather than function object, to handle addresses identified in instructions.
args:
addr (Address): the address of a function.
returns:
str: the function name
raises:
KeyError: when the given function does not have a name.
"""
raise KeyError(addr)
@abc.abstractmethod
def extract_function_features(self, f: FunctionHandle) -> Iterator[Tuple[Feature, Address]]:
"""
extract function-scope features.
the arguments are opaque values previously provided by `.get_functions()`, etc.
example::
extractor = VivisectFeatureExtractor(vw, path)
for function in extractor.get_functions():
for feature, address in extractor.extract_function_features(function):
print('0x%x: %s', address, feature)
args:
f [FunctionHandle]: an opaque value previously fetched from `.get_functions()`.
yields:
Tuple[Feature, Address]: feature and its location
"""
raise NotImplementedError()
@abc.abstractmethod
def get_basic_blocks(self, f: FunctionHandle) -> Iterator[BBHandle]:
"""
enumerate the basic blocks in the given function and provide opaque values that will
subsequently be provided to `.extract_basic_block_features()`, etc.
"""
raise NotImplementedError()
@abc.abstractmethod
def extract_basic_block_features(self, f: FunctionHandle, bb: BBHandle) -> Iterator[Tuple[Feature, Address]]:
"""
extract basic block-scope features.
the arguments are opaque values previously provided by `.get_functions()`, etc.
example::
extractor = VivisectFeatureExtractor(vw, path)
for function in extractor.get_functions():
for bb in extractor.get_basic_blocks(function):
for feature, address in extractor.extract_basic_block_features(function, bb):
print('0x%x: %s', address, feature)
args:
f [FunctionHandle]: an opaque value previously fetched from `.get_functions()`.
bb [BBHandle]: an opaque value previously fetched from `.get_basic_blocks()`.
yields:
Tuple[Feature, Address]: feature and its location
"""
raise NotImplementedError()
@abc.abstractmethod
def get_instructions(self, f: FunctionHandle, bb: BBHandle) -> Iterator[InsnHandle]:
"""
enumerate the instructions in the given basic block and provide opaque values that will
subsequently be provided to `.extract_insn_features()`, etc.
"""
raise NotImplementedError()
@abc.abstractmethod
def extract_insn_features(
self, f: FunctionHandle, bb: BBHandle, insn: InsnHandle
) -> Iterator[Tuple[Feature, Address]]:
"""
extract instruction-scope features.
the arguments are opaque values previously provided by `.get_functions()`, etc.
example::
extractor = VivisectFeatureExtractor(vw, path)
for function in extractor.get_functions():
for bb in extractor.get_basic_blocks(function):
for insn in extractor.get_instructions(function, bb):
for feature, address in extractor.extract_insn_features(function, bb, insn):
print('0x%x: %s', address, feature)
args:
f [FunctionHandle]: an opaque value previously fetched from `.get_functions()`.
bb [BBHandle]: an opaque value previously fetched from `.get_basic_blocks()`.
insn [InsnHandle]: an opaque value previously fetched from `.get_instructions()`.
yields:
Tuple[Feature, Address]: feature and its location
"""
raise NotImplementedError()

View File

@@ -0,0 +1,101 @@
import io
import logging
import binascii
import contextlib
from typing import Tuple, Iterator
import pefile
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.freeze import is_freeze
from capa.features.address import NO_ADDRESS, Address, FileOffsetAddress
logger = logging.getLogger(__name__)
def extract_file_strings(buf, **kwargs) -> Iterator[Tuple[String, Address]]:
"""
extract ASCII and UTF-16 LE strings from file
"""
for s in capa.features.extractors.strings.extract_ascii_strings(buf):
yield String(s.s), FileOffsetAddress(s.offset)
for s in capa.features.extractors.strings.extract_unicode_strings(buf):
yield String(s.s), FileOffsetAddress(s.offset)
def extract_format(buf) -> Iterator[Tuple[Feature, Address]]:
if buf.startswith(b"MZ"):
yield Format(FORMAT_PE), NO_ADDRESS
elif buf.startswith(b"\x7fELF"):
yield Format(FORMAT_ELF), NO_ADDRESS
elif is_freeze(buf):
yield Format(FORMAT_FREEZE), NO_ADDRESS
else:
# we likely end up here:
# 1. handling a file format (e.g. macho)
#
# for (1), this logic will need to be updated as the format is implemented.
logger.debug("unsupported file format: %s", binascii.hexlify(buf[:4]).decode("ascii"))
return
def extract_arch(buf) -> Iterator[Tuple[Feature, Address]]:
if buf.startswith(b"MZ"):
yield from capa.features.extractors.pefile.extract_file_arch(pe=pefile.PE(data=buf))
elif buf.startswith(b"\x7fELF"):
with contextlib.closing(io.BytesIO(buf)) as f:
arch = capa.features.extractors.elf.detect_elf_arch(f)
if arch not in capa.features.common.VALID_ARCH:
logger.debug("unsupported arch: %s", arch)
return
yield Arch(arch), 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 arch,
# but i think this would be rarely used.
# rules that rely on arch 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 Arch", binascii.hexlify(buf[:4]).decode("ascii"))
return
def extract_os(buf) -> Iterator[Tuple[Feature, Address]]:
if buf.startswith(b"MZ"):
yield OS(OS_WINDOWS), NO_ADDRESS
elif buf.startswith(b"\x7fELF"):
with contextlib.closing(io.BytesIO(buf)) as f:
os = capa.features.extractors.elf.detect_elf_os(f)
if os not in capa.features.common.VALID_OS:
logger.debug("unsupported os: %s", os)
return
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", binascii.hexlify(buf[:4]).decode("ascii"))
return

View File

@@ -0,0 +1,154 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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 __future__ import annotations
from typing import Dict, List, Tuple, Union, Iterator, Optional
import dnfile
from dncil.cil.opcode import OpCodes
import capa.features.extractors
import capa.features.extractors.dotnetfile
import capa.features.extractors.dnfile.file
import capa.features.extractors.dnfile.insn
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.dnfile.helpers import (
get_dotnet_types,
get_dotnet_fields,
get_dotnet_managed_imports,
get_dotnet_managed_methods,
get_dotnet_unmanaged_imports,
get_dotnet_managed_method_bodies,
)
class DnFileFeatureExtractorCache:
def __init__(self, pe: dnfile.dnPE):
self.imports: Dict[int, Union[DnType, DnUnmanagedMethod]] = {}
self.native_imports: Dict[int, Union[DnType, DnUnmanagedMethod]] = {}
self.methods: Dict[int, Union[DnType, DnUnmanagedMethod]] = {}
self.fields: Dict[int, Union[DnType, DnUnmanagedMethod]] = {}
self.types: Dict[int, Union[DnType, DnUnmanagedMethod]] = {}
for import_ in get_dotnet_managed_imports(pe):
self.imports[import_.token] = import_
for native_import in get_dotnet_unmanaged_imports(pe):
self.native_imports[native_import.token] = native_import
for method in get_dotnet_managed_methods(pe):
self.methods[method.token] = method
for field in get_dotnet_fields(pe):
self.fields[field.token] = field
for type_ in get_dotnet_types(pe):
self.types[type_.token] = type_
def get_import(self, token: int) -> Optional[Union[DnType, DnUnmanagedMethod]]:
return self.imports.get(token, None)
def get_native_import(self, token: int) -> Optional[Union[DnType, DnUnmanagedMethod]]:
return self.native_imports.get(token, None)
def get_method(self, token: int) -> Optional[Union[DnType, DnUnmanagedMethod]]:
return self.methods.get(token, None)
def get_field(self, token: int) -> Optional[Union[DnType, DnUnmanagedMethod]]:
return self.fields.get(token, None)
def get_type(self, token: int) -> Optional[Union[DnType, DnUnmanagedMethod]]:
return self.types.get(token, None)
class DnfileFeatureExtractor(FeatureExtractor):
def __init__(self, path: str):
super().__init__()
self.pe: dnfile.dnPE = dnfile.dnPE(path)
# pre-compute .NET token lookup tables; each .NET method has access to this cache for feature extraction
# most relevant at instruction scope
self.token_cache: DnFileFeatureExtractorCache = DnFileFeatureExtractorCache(self.pe)
# pre-compute these because we'll yield them at *every* scope.
self.global_features: List[Tuple[Feature, Address]] = []
self.global_features.extend(capa.features.extractors.dotnetfile.extract_file_format())
self.global_features.extend(capa.features.extractors.dotnetfile.extract_file_os(pe=self.pe))
self.global_features.extend(capa.features.extractors.dotnetfile.extract_file_arch(pe=self.pe))
def get_base_address(self):
return NO_ADDRESS
def extract_global_features(self):
yield from self.global_features
def extract_file_features(self):
yield from capa.features.extractors.dnfile.file.extract_features(self.pe)
def get_functions(self) -> Iterator[FunctionHandle]:
# create a method lookup table
methods: Dict[Address, FunctionHandle] = {}
for token, method in get_dotnet_managed_method_bodies(self.pe):
fh: FunctionHandle = FunctionHandle(
address=DNTokenAddress(token),
inner=method,
ctx={"pe": self.pe, "calls_from": set(), "calls_to": set(), "cache": self.token_cache},
)
# method tokens should be unique
assert fh.address not in methods.keys()
methods[fh.address] = fh
# calculate unique calls to/from each method
for fh in methods.values():
for insn in fh.inner.instructions:
if insn.opcode not in (
OpCodes.Call,
OpCodes.Callvirt,
OpCodes.Jmp,
OpCodes.Newobj,
):
continue
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)
if dest is not None:
dest.ctx["calls_to"].add(fh.address)
# record call from source method; note: we record all unique calls from a MethodDef method, not just
# those calls to other MethodDef methods e.g. calls to imported MemberRef methods
fh.ctx["calls_from"].add(address)
yield from methods.values()
def extract_function_features(self, fh) -> Iterator[Tuple[Feature, Address]]:
yield from capa.features.extractors.dnfile.function.extract_features(fh)
def get_basic_blocks(self, f) -> Iterator[BBHandle]:
# each dotnet method is considered 1 basic block
yield BBHandle(
address=f.address,
inner=f.inner,
)
def extract_basic_block_features(self, fh, bbh):
# we don't support basic block features
yield from []
def get_instructions(self, fh, bbh):
for insn in bbh.inner.instructions:
yield InsnHandle(
address=DNTokenOffsetAddress(bbh.address, insn.offset - (fh.inner.offset + fh.inner.header_size)),
inner=insn,
)
def extract_insn_features(self, fh, bbh, ih) -> Iterator[Tuple[Feature, Address]]:
yield from capa.features.extractors.dnfile.insn.extract_features(fh, bbh, ih)

View File

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

View File

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

View File

@@ -0,0 +1,335 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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 __future__ import annotations
import logging
from typing import Dict, Tuple, Union, Iterator, Optional
import dnfile
from dncil.cil.body import CilMethodBody
from dncil.cil.error import MethodBodyFormatError
from dncil.clr.token import Token, StringToken, InvalidToken
from dncil.cil.body.reader import CilMethodBodyReaderBase
from capa.features.common import FeatureAccess
from capa.features.extractors.dnfile.types import DnType, DnUnmanagedMethod
logger = logging.getLogger(__name__)
class DnfileMethodBodyReader(CilMethodBodyReaderBase):
def __init__(self, pe: dnfile.dnPE, row: dnfile.mdtable.MethodDefRow):
self.pe: dnfile.dnPE = pe
self.offset: int = self.pe.get_offset_from_rva(row.Rva)
def read(self, n: int) -> bytes:
data: bytes = self.pe.get_data(self.pe.get_rva_from_offset(self.offset), n)
self.offset += n
return data
def tell(self) -> int:
return self.offset
def seek(self, offset: int) -> int:
self.offset = offset
return self.offset
def resolve_dotnet_token(pe: dnfile.dnPE, token: Token) -> Union[dnfile.base.MDTableRow, InvalidToken, str]:
"""map generic token to string or table row"""
assert pe.net is not None
assert pe.net.mdtables is not None
if isinstance(token, StringToken):
user_string: Optional[str] = read_dotnet_user_string(pe, token)
if user_string is None:
return InvalidToken(token.value)
return user_string
table: Optional[dnfile.base.ClrMetaDataTable] = pe.net.mdtables.tables.get(token.table, None)
if table is None:
# table index is not valid
return InvalidToken(token.value)
try:
return table.rows[token.rid - 1]
except IndexError:
# table index is valid but row index is not valid
return InvalidToken(token.value)
def read_dotnet_method_body(pe: dnfile.dnPE, row: dnfile.mdtable.MethodDefRow) -> Optional[CilMethodBody]:
"""read dotnet method body"""
try:
return CilMethodBody(DnfileMethodBodyReader(pe, row))
except MethodBodyFormatError as e:
logger.debug("failed to parse managed method body @ 0x%08x (%s)", row.Rva, e)
return None
def read_dotnet_user_string(pe: dnfile.dnPE, token: StringToken) -> Optional[str]:
"""read user string from #US stream"""
assert pe.net is not None
if pe.net.user_strings is None:
# stream may not exist (seen in obfuscated .NET)
logger.debug("#US stream does not exist for stream index 0x%08x", token.rid)
return None
try:
user_string: Optional[dnfile.stream.UserString] = pe.net.user_strings.get_us(token.rid)
except UnicodeDecodeError as e:
logger.debug("failed to decode #US stream index 0x%08x (%s)", token.rid, e)
return None
if user_string is None:
return None
return user_string.value
def get_dotnet_managed_imports(pe: dnfile.dnPE) -> Iterator[DnType]:
"""get managed imports from MemberRef table
see https://www.ntcore.com/files/dotnetformat.htm
10 - MemberRef Table
Each row represents an imported method
Class (index into the TypeRef, ModuleRef, MethodDef, TypeSpec or TypeDef tables)
Name (index into String heap)
01 - TypeRef Table
Each row represents an imported class, its namespace and the assembly which contains it
TypeName (index into String heap)
TypeNamespace (index into String heap)
"""
for rid, member_ref in iter_dotnet_table(pe, dnfile.mdtable.MemberRef.number):
assert isinstance(member_ref, dnfile.mdtable.MemberRefRow)
if not isinstance(member_ref.Class.row, dnfile.mdtable.TypeRefRow):
# only process class imports from TypeRef table
continue
token: int = calculate_dotnet_token_value(dnfile.mdtable.MemberRef.number, rid)
access: Optional[str]
# assume .NET imports starting with get_/set_ are used to access a property
if member_ref.Name.startswith("get_"):
access = FeatureAccess.READ
elif member_ref.Name.startswith("set_"):
access = FeatureAccess.WRITE
else:
access = None
member_ref_name: str = member_ref.Name
if member_ref_name.startswith(("get_", "set_")):
# remove get_/set_ from MemberRef name
member_ref_name = member_ref_name[4:]
yield DnType(
token,
member_ref.Class.row.TypeName,
namespace=member_ref.Class.row.TypeNamespace,
member=member_ref_name,
access=access,
)
def get_dotnet_methoddef_property_accessors(pe: dnfile.dnPE) -> Iterator[Tuple[int, str]]:
"""get MethodDef methods used to access properties
see https://www.ntcore.com/files/dotnetformat.htm
24 - MethodSemantics Table
Links Events and Properties to specific methods. For example one Event can be associated to more methods. A property uses this table to associate get/set methods.
Semantics (a 2-byte bitmask of type MethodSemanticsAttributes)
Method (index into the MethodDef table)
Association (index into the Event or Property table; more precisely, a HasSemantics coded index)
"""
for rid, method_semantics in iter_dotnet_table(pe, dnfile.mdtable.MethodSemantics.number):
assert isinstance(method_semantics, dnfile.mdtable.MethodSemanticsRow)
if method_semantics.Association.row is None:
logger.debug("MethodSemantics[0x%X] Association row is None", rid)
continue
if isinstance(method_semantics.Association.row, dnfile.mdtable.EventRow):
# ignore events
logger.debug("MethodSemantics[0x%X] ignoring Event", rid)
continue
if method_semantics.Method.table is None:
logger.debug("MethodSemantics[0x%X] Method table is None", rid)
continue
token: int = calculate_dotnet_token_value(
method_semantics.Method.table.number, method_semantics.Method.row_index
)
if method_semantics.Semantics.msSetter:
yield token, FeatureAccess.WRITE
elif method_semantics.Semantics.msGetter:
yield token, FeatureAccess.READ
def get_dotnet_managed_methods(pe: dnfile.dnPE) -> Iterator[DnType]:
"""get managed method names from TypeDef table
see https://www.ntcore.com/files/dotnetformat.htm
02 - TypeDef Table
Each row represents a class in the current assembly.
TypeName (index into String heap)
TypeNamespace (index into String heap)
MethodList (index into MethodDef table; it marks the first of a contiguous run of Methods owned by this Type)
"""
accessor_map: Dict[int, str] = {}
for methoddef, methoddef_access in get_dotnet_methoddef_property_accessors(pe):
accessor_map[methoddef] = methoddef_access
for rid, typedef in iter_dotnet_table(pe, dnfile.mdtable.TypeDef.number):
assert isinstance(typedef, dnfile.mdtable.TypeDefRow)
for idx, method in enumerate(typedef.MethodList):
if method.table is None:
logger.debug("TypeDef[0x%X] MethodList[0x%X] table is None", rid, idx)
continue
if method.row is None:
logger.debug("TypeDef[0x%X] MethodList[0x%X] row is None", rid, idx)
continue
token: int = calculate_dotnet_token_value(method.table.number, method.row_index)
access: Optional[str] = accessor_map.get(token, None)
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)
def get_dotnet_fields(pe: dnfile.dnPE) -> Iterator[DnType]:
"""get fields from TypeDef table
see https://www.ntcore.com/files/dotnetformat.htm
02 - TypeDef Table
Each row represents a class in the current assembly.
TypeName (index into String heap)
TypeNamespace (index into String heap)
FieldList (index into Field table; it marks the first of a contiguous run of Fields owned by this Type)
"""
for rid, typedef in iter_dotnet_table(pe, dnfile.mdtable.TypeDef.number):
assert isinstance(typedef, dnfile.mdtable.TypeDefRow)
for idx, field in enumerate(typedef.FieldList):
if field.table is None:
logger.debug("TypeDef[0x%X] FieldList[0x%X] table is None", rid, idx)
continue
if field.row is None:
logger.debug("TypeDef[0x%X] FieldList[0x%X] row is None", rid, idx)
continue
token: int = calculate_dotnet_token_value(field.table.number, field.row_index)
yield DnType(token, typedef.TypeName, namespace=typedef.TypeNamespace, member=field.row.Name)
def get_dotnet_managed_method_bodies(pe: dnfile.dnPE) -> Iterator[Tuple[int, CilMethodBody]]:
"""get managed methods from MethodDef table"""
for rid, method_def in iter_dotnet_table(pe, dnfile.mdtable.MethodDef.number):
assert isinstance(method_def, dnfile.mdtable.MethodDefRow)
if not method_def.ImplFlags.miIL or any((method_def.Flags.mdAbstract, method_def.Flags.mdPinvokeImpl)):
# skip methods that do not have a method body
continue
body: Optional[CilMethodBody] = read_dotnet_method_body(pe, method_def)
if body is None:
logger.debug("MethodDef[0x%X] method body is None", rid)
continue
token: int = calculate_dotnet_token_value(dnfile.mdtable.MethodDef.number, rid)
yield token, body
def get_dotnet_unmanaged_imports(pe: dnfile.dnPE) -> Iterator[DnUnmanagedMethod]:
"""get unmanaged imports from ImplMap table
see https://www.ntcore.com/files/dotnetformat.htm
28 - ImplMap Table
ImplMap table holds information about unmanaged methods that can be reached from managed code, using PInvoke dispatch
MemberForwarded (index into the Field or MethodDef table; more precisely, a MemberForwarded coded index)
ImportName (index into the String heap)
ImportScope (index into the ModuleRef table)
"""
for rid, impl_map in iter_dotnet_table(pe, dnfile.mdtable.ImplMap.number):
assert isinstance(impl_map, dnfile.mdtable.ImplMapRow)
module: str
if impl_map.ImportScope.row is None:
logger.debug("ImplMap[0x%X] ImportScope row is None", rid)
module = ""
else:
module = impl_map.ImportScope.row.Name
method: str = impl_map.ImportName
member_forward_table: int
if impl_map.MemberForwarded.table is None:
logger.debug("ImplMap[0x%X] MemberForwarded table is None", rid)
continue
else:
member_forward_table = impl_map.MemberForwarded.table.number
member_forward_row: int = impl_map.MemberForwarded.row_index
# ECMA says "Each row of the ImplMap table associates a row in the MethodDef table (MemberForwarded) with the
# name of a routine (ImportName) in some unmanaged DLL (ImportScope)"; so we calculate and map the MemberForwarded
# MethodDef table token to help us later record native import method calls made from CIL
token: int = calculate_dotnet_token_value(member_forward_table, member_forward_row)
# like Kernel32.dll
if module and "." in module:
module = module.split(".")[0]
# like kernel32.CreateFileA
yield DnUnmanagedMethod(token, module, method)
def get_dotnet_types(pe: dnfile.dnPE) -> Iterator[DnType]:
"""get .NET types from TypeDef and TypeRef tables"""
for rid, typedef in iter_dotnet_table(pe, dnfile.mdtable.TypeDef.number):
assert isinstance(typedef, dnfile.mdtable.TypeDefRow)
typedef_token: int = calculate_dotnet_token_value(dnfile.mdtable.TypeDef.number, rid)
yield DnType(typedef_token, typedef.TypeName, namespace=typedef.TypeNamespace)
for rid, typeref in iter_dotnet_table(pe, dnfile.mdtable.TypeRef.number):
assert isinstance(typeref, dnfile.mdtable.TypeRefRow)
typeref_token: int = calculate_dotnet_token_value(dnfile.mdtable.TypeRef.number, rid)
yield DnType(typeref_token, typeref.TypeName, namespace=typeref.TypeNamespace)
def calculate_dotnet_token_value(table: int, rid: int) -> int:
return ((table & 0xFF) << Token.TABLE_SHIFT) | (rid & Token.RID_MASK)
def is_dotnet_mixed_mode(pe: dnfile.dnPE) -> bool:
assert pe.net is not None
assert pe.net.Flags is not None
return not bool(pe.net.Flags.CLR_ILONLY)
def iter_dotnet_table(pe: dnfile.dnPE, table_index: int) -> Iterator[Tuple[int, dnfile.base.MDTableRow]]:
assert pe.net is not None
assert pe.net.mdtables is not None
for rid, row in enumerate(pe.net.mdtables.tables.get(table_index, [])):
# .NET tables are 1-indexed
yield rid + 1, row

View File

@@ -0,0 +1,227 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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 __future__ import annotations
import logging
from typing import TYPE_CHECKING, Any, Dict, Tuple, Union, Iterator, Optional
if TYPE_CHECKING:
from capa.features.extractors.dnfile.extractor import DnFileFeatureExtractorCache
import dnfile
from dncil.clr.token import Token, StringToken, InvalidToken
from dncil.cil.opcode import OpCodes
import capa.features.extractors.helpers
from capa.features.insn import API, Number, Property
from capa.features.common import Class, String, Feature, Namespace, FeatureAccess, Characteristic
from capa.features.address import Address
from capa.features.extractors.dnfile.types import DnType, DnUnmanagedMethod
from capa.features.extractors.base_extractor import BBHandle, InsnHandle, FunctionHandle
from capa.features.extractors.dnfile.helpers import (
resolve_dotnet_token,
read_dotnet_user_string,
calculate_dotnet_token_value,
)
logger = logging.getLogger(__name__)
def get_callee(
pe: dnfile.dnPE, cache: DnFileFeatureExtractorCache, token: Token
) -> Optional[Union[DnType, DnUnmanagedMethod]]:
"""map .NET token to un/managed (generic) method"""
token_: int
if token.table == dnfile.mdtable.MethodSpec.number:
# map MethodSpec to MethodDef or MemberRef
row: Union[dnfile.base.MDTableRow, InvalidToken, str] = resolve_dotnet_token(pe, token)
assert isinstance(row, dnfile.mdtable.MethodSpecRow)
if row.Method.table is None:
logger.debug("MethodSpec[0x%X] Method table is None", token.rid)
return None
token_ = calculate_dotnet_token_value(row.Method.table.number, row.Method.row_index)
else:
token_ = token.value
callee: Optional[Union[DnType, DnUnmanagedMethod]] = cache.get_import(token_)
if callee is None:
# we must check unmanaged imports before managed methods because we map forwarded managed methods
# to their unmanaged imports; we prefer a forwarded managed method be mapped to its unmanaged import for analysis
callee = cache.get_native_import(token_)
if callee is None:
callee = cache.get_method(token_)
return callee
def extract_insn_api_features(fh: FunctionHandle, bh, ih: InsnHandle) -> Iterator[Tuple[Feature, Address]]:
"""parse instruction API features"""
if ih.inner.opcode not in (
OpCodes.Call,
OpCodes.Callvirt,
OpCodes.Jmp,
OpCodes.Newobj,
):
return
callee: Optional[Union[DnType, DnUnmanagedMethod]] = get_callee(fh.ctx["pe"], fh.ctx["cache"], ih.inner.operand)
if isinstance(callee, DnType):
# ignore methods used to access properties
if callee.access is None:
# like System.IO.File::Delete
yield API(str(callee)), ih.address
elif isinstance(callee, DnUnmanagedMethod):
# like kernel32.CreateFileA
for name in capa.features.extractors.helpers.generate_symbols(callee.module, callee.method):
yield API(name), ih.address
def extract_insn_property_features(fh: FunctionHandle, bh, ih: InsnHandle) -> Iterator[Tuple[Feature, Address]]:
"""parse instruction property features"""
name: Optional[str] = None
access: Optional[str] = None
if ih.inner.opcode in (OpCodes.Call, OpCodes.Callvirt, OpCodes.Jmp):
# property access via MethodDef or MemberRef
callee: Optional[Union[DnType, DnUnmanagedMethod]] = get_callee(fh.ctx["pe"], fh.ctx["cache"], ih.inner.operand)
if isinstance(callee, DnType):
if callee.access is not None:
name = str(callee)
access = callee.access
elif ih.inner.opcode in (OpCodes.Ldfld, OpCodes.Ldflda, OpCodes.Ldsfld, OpCodes.Ldsflda):
# property read via Field
read_field: Optional[Union[DnType, DnUnmanagedMethod]] = fh.ctx["cache"].get_field(ih.inner.operand.value)
if read_field is not None:
name = str(read_field)
access = FeatureAccess.READ
elif ih.inner.opcode in (OpCodes.Stfld, OpCodes.Stsfld):
# property write via Field
write_field: Optional[Union[DnType, DnUnmanagedMethod]] = fh.ctx["cache"].get_field(ih.inner.operand.value)
if write_field is not None:
name = str(write_field)
access = FeatureAccess.WRITE
if name is not None:
if access is not None:
yield Property(name, access=access), ih.address
yield Property(name), ih.address
def extract_insn_namespace_class_features(
fh: FunctionHandle, bh, ih: InsnHandle
) -> Iterator[Tuple[Union[Namespace, Class], Address]]:
"""parse instruction namespace and class features"""
type_: Optional[Union[DnType, DnUnmanagedMethod]] = None
if ih.inner.opcode in (
OpCodes.Call,
OpCodes.Callvirt,
OpCodes.Jmp,
OpCodes.Ldvirtftn,
OpCodes.Ldftn,
OpCodes.Newobj,
):
# method call - includes managed methods (MethodDef, TypeRef) and properties (MethodSemantics, TypeRef)
type_ = get_callee(fh.ctx["pe"], fh.ctx["cache"], ih.inner.operand)
elif ih.inner.opcode in (
OpCodes.Ldfld,
OpCodes.Ldflda,
OpCodes.Ldsfld,
OpCodes.Ldsflda,
OpCodes.Stfld,
OpCodes.Stsfld,
):
# field access
type_ = fh.ctx["cache"].get_field(ih.inner.operand.value)
# ECMA 335 VI.C.4.10
elif ih.inner.opcode in (
OpCodes.Initobj,
OpCodes.Box,
OpCodes.Castclass,
OpCodes.Cpobj,
OpCodes.Isinst,
OpCodes.Ldelem,
OpCodes.Ldelema,
OpCodes.Ldobj,
OpCodes.Mkrefany,
OpCodes.Newarr,
OpCodes.Refanyval,
OpCodes.Sizeof,
OpCodes.Stobj,
OpCodes.Unbox,
OpCodes.Constrained,
OpCodes.Stelem,
OpCodes.Unbox_Any,
):
# type access
type_ = fh.ctx["cache"].get_type(ih.inner.operand.value)
if isinstance(type_, DnType):
yield Class(DnType.format_name(type_.class_, namespace=type_.namespace)), ih.address
if type_.namespace:
yield Namespace(type_.namespace), ih.address
def extract_insn_number_features(fh, bh, ih: InsnHandle) -> Iterator[Tuple[Feature, Address]]:
"""parse instruction number features"""
if ih.inner.is_ldc():
yield Number(ih.inner.get_ldc()), ih.address
def extract_insn_string_features(fh: FunctionHandle, bh, ih: InsnHandle) -> Iterator[Tuple[Feature, Address]]:
"""parse instruction string features"""
if not ih.inner.is_ldstr():
return
if not isinstance(ih.inner.operand, StringToken):
return
user_string: Optional[str] = read_dotnet_user_string(fh.ctx["pe"], ih.inner.operand)
if user_string is None:
return
if len(user_string) >= 4:
yield String(user_string), ih.address
def extract_unmanaged_call_characteristic_features(
fh: FunctionHandle, bb: BBHandle, ih: InsnHandle
) -> Iterator[Tuple[Characteristic, Address]]:
if ih.inner.opcode not in (OpCodes.Call, OpCodes.Callvirt, OpCodes.Jmp):
return
row: Union[str, InvalidToken, dnfile.base.MDTableRow] = resolve_dotnet_token(fh.ctx["pe"], ih.inner.operand)
if not isinstance(row, dnfile.mdtable.MethodDefRow):
return
if any((row.Flags.mdPinvokeImpl, row.ImplFlags.miUnmanaged, row.ImplFlags.miNative)):
yield Characteristic("unmanaged call"), ih.address
def extract_features(fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle) -> Iterator[Tuple[Feature, Address]]:
"""extract instruction features"""
for inst_handler in INSTRUCTION_HANDLERS:
for feature, addr in inst_handler(fh, bbh, ih):
assert isinstance(addr, Address)
yield feature, addr
INSTRUCTION_HANDLERS = (
extract_insn_api_features,
extract_insn_property_features,
extract_insn_number_features,
extract_insn_string_features,
extract_insn_namespace_class_features,
extract_unmanaged_call_characteristic_features,
)

View File

@@ -0,0 +1,75 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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 Union, Optional
class DnType(object):
def __init__(self, token: int, class_: 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_
if member == ".ctor":
member = "ctor"
if member == ".cctor":
member = "cctor"
self.member: str = member
def __hash__(self):
return hash((self.token, self.access, self.namespace, self.class_, self.member))
def __eq__(self, other):
return (
self.token == other.token
and self.access == other.access
and self.namespace == other.namespace
and self.class_ == other.class_
and self.member == other.member
)
def __str__(self):
return DnType.format_name(self.class_, namespace=self.namespace, member=self.member)
def __repr__(self):
return str(self)
@staticmethod
def format_name(class_: str, namespace: str = "", member: str = ""):
# like File::OpenRead
name: str = f"{class_}::{member}" if member else class_
if namespace:
# like System.IO.File::OpenRead
name = f"{namespace}.{name}"
return name
class DnUnmanagedMethod:
def __init__(self, token: int, module: str, method: str):
self.token: int = token
self.module: str = module
self.method: str = method
def __hash__(self):
return hash((self.token, self.module, self.method))
def __eq__(self, other):
return self.token == other.token and self.module == other.module and self.method == other.method
def __str__(self):
return DnUnmanagedMethod.format_name(self.module, self.method)
def __repr__(self):
return str(self)
@staticmethod
def format_name(module, method):
return f"{module}.{method}"

View File

@@ -0,0 +1,150 @@
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

@@ -0,0 +1,231 @@
import logging
from typing import Tuple, Iterator, cast
import dnfile
import pefile
import capa.features.extractors.helpers
from capa.features.file import Import, FunctionName
from capa.features.common import (
OS,
OS_ANY,
ARCH_ANY,
ARCH_I386,
FORMAT_PE,
ARCH_AMD64,
FORMAT_DOTNET,
Arch,
Class,
Format,
String,
Feature,
Namespace,
Characteristic,
)
from capa.features.address import NO_ADDRESS, Address, DNTokenAddress
from capa.features.extractors.base_extractor import FeatureExtractor
from capa.features.extractors.dnfile.helpers import (
DnType,
iter_dotnet_table,
is_dotnet_mixed_mode,
get_dotnet_managed_imports,
get_dotnet_managed_methods,
calculate_dotnet_token_value,
get_dotnet_unmanaged_imports,
)
logger = logging.getLogger(__name__)
def extract_file_format(**kwargs) -> Iterator[Tuple[Format, Address]]:
yield Format(FORMAT_PE), NO_ADDRESS
yield Format(FORMAT_DOTNET), NO_ADDRESS
def extract_file_import_names(pe: dnfile.dnPE, **kwargs) -> Iterator[Tuple[Import, Address]]:
for method in get_dotnet_managed_imports(pe):
# like System.IO.File::OpenRead
yield Import(str(method)), DNTokenAddress(method.token)
for imp in get_dotnet_unmanaged_imports(pe):
# like kernel32.CreateFileA
for name in capa.features.extractors.helpers.generate_symbols(imp.module, imp.method):
yield Import(name), DNTokenAddress(imp.token)
def extract_file_function_names(pe: dnfile.dnPE, **kwargs) -> Iterator[Tuple[FunctionName, Address]]:
for method in get_dotnet_managed_methods(pe):
yield FunctionName(str(method)), DNTokenAddress(method.token)
def extract_file_namespace_features(pe: dnfile.dnPE, **kwargs) -> Iterator[Tuple[Namespace, Address]]:
"""emit namespace features from TypeRef and TypeDef tables"""
# namespaces may be referenced multiple times, so we need to filter
namespaces = set()
for _, typedef in iter_dotnet_table(pe, dnfile.mdtable.TypeDef.number):
# emit internal .NET namespaces
assert isinstance(typedef, dnfile.mdtable.TypeDefRow)
namespaces.add(typedef.TypeNamespace)
for _, typeref in iter_dotnet_table(pe, dnfile.mdtable.TypeRef.number):
# emit external .NET namespaces
assert isinstance(typeref, dnfile.mdtable.TypeRefRow)
namespaces.add(typeref.TypeNamespace)
# namespaces may be empty, discard
namespaces.discard("")
for namespace in namespaces:
# namespace do not have an associated token, so we yield 0x0
yield Namespace(namespace), NO_ADDRESS
def extract_file_class_features(pe: dnfile.dnPE, **kwargs) -> Iterator[Tuple[Class, Address]]:
"""emit class features from TypeRef and TypeDef tables"""
for rid, typedef in iter_dotnet_table(pe, dnfile.mdtable.TypeDef.number):
# emit internal .NET classes
assert isinstance(typedef, dnfile.mdtable.TypeDefRow)
token = calculate_dotnet_token_value(dnfile.mdtable.TypeDef.number, rid)
yield Class(DnType.format_name(typedef.TypeName, namespace=typedef.TypeNamespace)), DNTokenAddress(token)
for rid, typeref in iter_dotnet_table(pe, dnfile.mdtable.TypeRef.number):
# emit external .NET classes
assert isinstance(typeref, dnfile.mdtable.TypeRefRow)
token = calculate_dotnet_token_value(dnfile.mdtable.TypeRef.number, rid)
yield Class(DnType.format_name(typeref.TypeName, namespace=typeref.TypeNamespace)), DNTokenAddress(token)
def extract_file_os(**kwargs) -> Iterator[Tuple[OS, Address]]:
yield OS(OS_ANY), NO_ADDRESS
def extract_file_arch(pe: dnfile.dnPE, **kwargs) -> Iterator[Tuple[Arch, Address]]:
# 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_strings(pe: dnfile.dnPE, **kwargs) -> Iterator[Tuple[String, Address]]:
yield from capa.features.extractors.common.extract_file_strings(pe.__data__)
def extract_file_mixed_mode_characteristic_features(
pe: dnfile.dnPE, **kwargs
) -> Iterator[Tuple[Characteristic, Address]]:
if is_dotnet_mixed_mode(pe):
yield Characteristic("mixed mode"), NO_ADDRESS
def extract_file_features(pe: dnfile.dnPE) -> Iterator[Tuple[Feature, Address]]:
for file_handler in FILE_HANDLERS:
for feature, addr in file_handler(pe=pe): # type: ignore
yield feature, addr
FILE_HANDLERS = (
extract_file_import_names,
extract_file_function_names,
extract_file_strings,
extract_file_format,
extract_file_mixed_mode_characteristic_features,
extract_file_namespace_features,
extract_file_class_features,
)
def extract_global_features(pe: dnfile.dnPE) -> Iterator[Tuple[Feature, Address]]:
for handler in GLOBAL_HANDLERS:
for feature, va in handler(pe=pe): # type: ignore
yield feature, va
GLOBAL_HANDLERS = (
extract_file_os,
extract_file_arch,
)
class DotnetFileFeatureExtractor(FeatureExtractor):
def __init__(self, path: str):
super().__init__()
self.path: str = path
self.pe: dnfile.dnPE = dnfile.dnPE(path)
def get_base_address(self):
return NO_ADDRESS
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:
return is_dotnet_mixed_mode(self.pe)
def get_runtime_version(self) -> Tuple[int, int]:
assert self.pe.net is not None
assert self.pe.net.struct is not None
assert self.pe.net.struct.MajorRuntimeVersion is not None
assert self.pe.net.struct.MinorRuntimeVersion 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("DotnetFileFeatureExtractor can only be used to extract file features")
def extract_function_features(self, f):
raise NotImplementedError("DotnetFileFeatureExtractor can only be used to extract file features")
def get_basic_blocks(self, f):
raise NotImplementedError("DotnetFileFeatureExtractor can only be used to extract file features")
def extract_basic_block_features(self, f, bb):
raise NotImplementedError("DotnetFileFeatureExtractor can only be used to extract file features")
def get_instructions(self, f, bb):
raise NotImplementedError("DotnetFileFeatureExtractor can only be used to extract file features")
def extract_insn_features(self, f, bb, insn):
raise NotImplementedError("DotnetFileFeatureExtractor can only be used to extract file features")
def is_library_function(self, va):
raise NotImplementedError("DotnetFileFeatureExtractor can only be used to extract file features")
def get_function_name(self, va):
raise NotImplementedError("DotnetFileFeatureExtractor can only be used to extract file features")

View File

@@ -0,0 +1,781 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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
import logging
import itertools
import collections
from enum import Enum
from typing import Set, Dict, List, Tuple, BinaryIO, Iterator, Optional
from dataclasses import dataclass
logger = logging.getLogger(__name__)
def align(v, alignment):
remainder = v % alignment
if remainder == 0:
return v
else:
return v + (alignment - remainder)
def read_cstr(buf, offset):
s = buf[offset:]
s, _, _ = s.partition(b"\x00")
return s.decode("utf-8")
class CorruptElfFile(ValueError):
pass
class OS(str, Enum):
HPUX = "hpux"
NETBSD = "netbsd"
LINUX = "linux"
HURD = "hurd"
_86OPEN = "86open"
SOLARIS = "solaris"
AIX = "aix"
IRIX = "irix"
FREEBSD = "freebsd"
TRU64 = "tru64"
MODESTO = "modesto"
OPENBSD = "openbsd"
OPENVMS = "openvms"
NSK = "nsk"
AROS = "aros"
FENIXOS = "fenixos"
CLOUD = "cloud"
SYLLABLE = "syllable"
NACL = "nacl"
# via readelf: https://github.com/bminor/binutils-gdb/blob/c0e94211e1ac05049a4ce7c192c9d14d1764eb3e/binutils/readelf.c#L19635-L19658
# and here: https://github.com/bminor/binutils-gdb/blob/34c54daa337da9fadf87d2706d6a590ae1f88f4d/include/elf/common.h#L933-L939
GNU_ABI_TAG = {
0: OS.LINUX,
1: OS.HURD,
2: OS.SOLARIS,
3: OS.FREEBSD,
4: OS.NETBSD,
5: OS.SYLLABLE,
6: OS.NACL,
}
@dataclass
class Phdr:
type: int
offset: int
vaddr: int
paddr: int
filesz: int
buf: bytes
@dataclass
class Shdr:
name: int
type: int
flags: int
addr: int
offset: int
size: int
link: int
buf: bytes
class ELF:
def __init__(self, f: BinaryIO):
self.f = f
# these will all be initialized in `_parse()`
self.bitness: int
self.endian: str
self.e_phentsize: int
self.e_phnum: int
self.e_shentsize: int
self.e_shnum: int
self.phbuf: bytes
self.shbuf: bytes
self._parse()
def _parse(self):
self.f.seek(0x0)
self.file_header = self.f.read(0x40)
if not self.file_header.startswith(b"\x7fELF"):
raise CorruptElfFile("missing magic header")
ei_class, ei_data = struct.unpack_from("BB", self.file_header, 4)
logger.debug("ei_class: 0x%02x ei_data: 0x%02x", ei_class, ei_data)
if ei_class == 1:
self.bitness = 32
elif ei_class == 2:
self.bitness = 64
else:
raise CorruptElfFile("invalid ei_class: 0x%02x" % ei_class)
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)
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)
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)
else:
raise NotImplementedError()
logger.debug("e_phoff: 0x%02x e_phentsize: 0x%02x e_phnum: %d", e_phoff, self.e_phentsize, self.e_phnum)
self.f.seek(e_phoff)
program_header_size = self.e_phnum * self.e_phentsize
self.phbuf = self.f.read(program_header_size)
if len(self.phbuf) != program_header_size:
logger.warning("failed to read program headers")
self.e_phnum = 0
self.f.seek(e_shoff)
section_header_size = self.e_shnum * self.e_shentsize
self.shbuf = self.f.read(section_header_size)
if len(self.shbuf) != section_header_size:
logger.warning("failed to read section headers")
self.e_shnum = 0
OSABI = {
# via pyelftools: https://github.com/eliben/pyelftools/blob/0664de05ed2db3d39041e2d51d19622a8ef4fb0f/elftools/elf/enums.py#L35-L58
# some candidates are commented out because the are not useful values,
# at least when guessing OSes
# 0: "SYSV", # too often used when OS is not SYSV
1: OS.HPUX,
2: OS.NETBSD,
3: OS.LINUX,
4: OS.HURD,
5: OS._86OPEN,
6: OS.SOLARIS,
7: OS.AIX,
8: OS.IRIX,
9: OS.FREEBSD,
10: OS.TRU64,
11: OS.MODESTO,
12: OS.OPENBSD,
13: OS.OPENVMS,
14: OS.NSK,
15: OS.AROS,
16: OS.FENIXOS,
17: OS.CLOUD,
# 53: "SORTFIX", # i can't find any reference to this OS, i dont think it exists
# 64: "ARM_AEABI", # not an OS
# 97: "ARM", # not an OS
# 255: "STANDALONE", # not an OS
}
@property
def ei_osabi(self) -> Optional[OS]:
(ei_osabi,) = struct.unpack_from(self.endian + "B", self.file_header, 7)
return ELF.OSABI.get(ei_osabi)
MACHINE = {
# via https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html
1: "M32",
2: "SPARC",
3: "i386",
4: "68K",
5: "88K",
6: "486",
7: "860",
8: "MIPS",
9: "S370",
10: "MIPS_RS3_LE",
11: "RS6000",
15: "PA_RISC",
16: "nCUBE",
17: "VPP500",
18: "SPARC32PLUS",
19: "960",
20: "PPC",
21: "PPC64",
22: "S390",
23: "SPU",
36: "V800",
37: "FR20",
38: "RH32",
39: "RCE",
40: "ARM",
41: "ALPHA",
42: "SH",
43: "SPARCV9",
44: "TRICORE",
45: "ARC",
46: "H8_300",
47: "H8_300H",
48: "H8S",
49: "H8_500",
50: "IA_64",
51: "MIPS_X",
52: "COLDFIRE",
53: "68HC12",
54: "MMA",
55: "PCP",
56: "NCPU",
57: "NDR1",
58: "STARCORE",
59: "ME16",
60: "ST100",
61: "TINYJ",
62: "amd64",
63: "PDSP",
64: "PDP10",
65: "PDP11",
66: "FX66",
67: "ST9PLUS",
68: "ST7",
69: "68HC16",
70: "68HC11",
71: "68HC08",
72: "68HC05",
73: "SVX",
74: "ST19",
75: "VAX",
76: "CRIS",
77: "JAVELIN",
78: "FIREPATH",
79: "ZSP",
80: "MMIX",
81: "HUANY",
82: "PRISM",
83: "AVR",
84: "FR30",
85: "D10V",
86: "D30V",
87: "V850",
88: "M32R",
89: "MN10300",
90: "MN10200",
91: "PJ",
92: "OPENRISC",
93: "ARC_A5",
94: "XTENSA",
95: "VIDEOCORE",
96: "TMM_GPP",
97: "NS32K",
98: "TPC",
99: "SNP1K",
100: "ST200",
}
@property
def e_machine(self) -> Optional[str]:
(e_machine,) = struct.unpack_from(self.endian + "H", self.file_header, 0x12)
return ELF.MACHINE.get(e_machine)
def parse_program_header(self, i) -> Phdr:
phent_offset = i * self.e_phentsize
phent = self.phbuf[phent_offset : phent_offset + self.e_phentsize]
(p_type,) = struct.unpack_from(self.endian + "I", phent, 0x0)
logger.debug("ph:p_type: 0x%04x", p_type)
if self.bitness == 32:
p_offset, p_vaddr, p_paddr, p_filesz = struct.unpack_from(self.endian + "IIII", phent, 0x4)
elif self.bitness == 64:
p_offset, p_vaddr, p_paddr, p_filesz = struct.unpack_from(self.endian + "QQQQ", phent, 0x8)
else:
raise NotImplementedError()
logger.debug("ph:p_offset: 0x%02x p_filesz: 0x%04x", p_offset, p_filesz)
self.f.seek(p_offset)
buf = self.f.read(p_filesz)
if len(buf) != p_filesz:
raise ValueError("failed to read program header content")
return Phdr(p_type, p_offset, p_vaddr, p_paddr, p_filesz, buf)
@property
def program_headers(self):
for i in range(self.e_phnum):
try:
yield self.parse_program_header(i)
except ValueError:
continue
def parse_section_header(self, i) -> Shdr:
shent_offset = i * self.e_shentsize
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
)
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
)
else:
raise NotImplementedError()
logger.debug("sh:sh_offset: 0x%02x sh_size: 0x%04x", sh_offset, sh_size)
self.f.seek(sh_offset)
buf = self.f.read(sh_size)
if len(buf) != sh_size:
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)
@property
def section_headers(self):
for i in range(self.e_shnum):
try:
yield self.parse_section_header(i)
except ValueError:
continue
@property
def linker(self):
PT_INTERP = 0x3
for phdr in self.program_headers:
if phdr.type != PT_INTERP:
continue
return read_cstr(phdr.buf, 0)
@property
def versions_needed(self) -> Dict[str, Set[str]]:
# symbol version requirements are stored in the .gnu.version_r section,
# which has type SHT_GNU_verneed (0x6ffffffe).
#
# this contains a linked list of ElfXX_Verneed structs,
# each referencing a linked list of ElfXX_Vernaux structs.
# strings are stored in the section referenced by the sh_link field of the section header.
# each Verneed struct contains a reference to the name of the library,
# each Vernaux struct contains a reference to the name of a symbol.
SHT_GNU_VERNEED = 0x6FFFFFFE
for shdr in self.section_headers:
if shdr.type != SHT_GNU_VERNEED:
continue
# the linked section contains strings referenced by the verneed structures.
linked_shdr = self.parse_section_header(shdr.link)
versions_needed = collections.defaultdict(set)
# read verneed structures from the start of the section
# until the vn_next link is 0x0.
# each entry describes a shared object that is required by this binary.
vn_offset = 0x0
while True:
# ElfXX_Verneed layout is the same on 32 and 64 bit
vn_version, vn_cnt, vn_file, vn_aux, vn_next = struct.unpack_from(
self.endian + "HHIII", shdr.buf, vn_offset
)
if vn_version != 1:
# unexpected format, don't try to keep parsing
break
# shared object names, like: "libdl.so.2"
so_name = read_cstr(linked_shdr.buf, vn_file)
# read vernaux structures linked from the verneed structure.
# 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):
# 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)
# ABI names, like: "GLIBC_2.2.5"
abi = read_cstr(linked_shdr.buf, vna_name)
versions_needed[so_name].add(abi)
vna_offset += vna_next
vn_offset += vn_next
if vn_next == 0:
break
return dict(versions_needed)
return {}
@property
def dynamic_entries(self) -> Iterator[Tuple[int, int]]:
"""
read the entries from the dynamic section,
yielding the tag and value for each entry.
"""
DT_NULL = 0x0
PT_DYNAMIC = 0x2
for phdr in self.program_headers:
if phdr.type != PT_DYNAMIC:
continue
offset = 0x0
while True:
if self.bitness == 32:
d_tag, d_val = struct.unpack_from(self.endian + "II", phdr.buf, offset)
offset += 8
elif self.bitness == 64:
d_tag, d_val = struct.unpack_from(self.endian + "QQ", phdr.buf, offset)
offset += 16
else:
raise NotImplementedError()
if d_tag == DT_NULL:
break
yield d_tag, d_val
@property
def strtab(self) -> Optional[bytes]:
"""
fetch the bytes of the string table
referenced by the dynamic section.
"""
DT_STRTAB = 0x5
DT_STRSZ = 0xA
strtab_addr = None
strtab_size = None
for d_tag, d_val in self.dynamic_entries:
if d_tag == DT_STRTAB:
strtab_addr = d_val
for d_tag, d_val in self.dynamic_entries:
if d_tag == DT_STRSZ:
strtab_size = d_val
if strtab_addr is None:
return None
if strtab_size is None:
return None
strtab_offset = None
for shdr in self.section_headers:
if shdr.addr <= strtab_addr < shdr.addr + shdr.size:
strtab_offset = shdr.offset + (strtab_addr - shdr.addr)
if strtab_offset is None:
return None
self.f.seek(strtab_offset)
strtab_buf = self.f.read(strtab_size)
if len(strtab_buf) != strtab_size:
return None
return strtab_buf
@property
def needed(self) -> Iterator[str]:
"""
read the names of DT_NEEDED entries from the dynamic section,
which correspond to dependencies on other shared objects,
like: `libpthread.so.0`
"""
DT_NEEDED = 0x1
strtab = self.strtab
if not strtab:
return
for d_tag, d_val in self.dynamic_entries:
if d_tag != DT_NEEDED:
continue
yield read_cstr(strtab, d_val)
@dataclass
class ABITag:
os: OS
kmajor: int
kminor: int
kpatch: int
class PHNote:
def __init__(self, endian: str, buf: bytes):
self.endian = endian
self.buf = buf
# these will be initialized in `_parse()`
self.type_: int
self.descsz: int
self.name: str
self._parse()
def _parse(self):
namesz, self.descsz, self.type_ = struct.unpack_from(self.endian + "III", self.buf, 0x0)
name_offset = 0xC
self.desc_offset = name_offset + align(namesz, 0x4)
logger.debug("ph:namesz: 0x%02x descsz: 0x%02x type: 0x%04x", namesz, self.descsz, self.type_)
self.name = self.buf[name_offset : name_offset + namesz].partition(b"\x00")[0].decode("ascii")
logger.debug("name: %s", self.name)
@property
def abi_tag(self) -> Optional[ABITag]:
if self.type_ != 1:
# > The type field shall be 1.
# Linux Standard Base Specification 1.2
# ref: https://refspecs.linuxfoundation.org/LSB_1.2.0/gLSB/noteabitag.html
return None
if self.name != "GNU":
return None
if self.descsz < 16:
return None
desc = self.buf[self.desc_offset : self.desc_offset + self.descsz]
abi_tag, kmajor, kminor, kpatch = struct.unpack_from(self.endian + "IIII", desc, 0x0)
logger.debug("GNU_ABI_TAG: 0x%02x", abi_tag)
os = GNU_ABI_TAG.get(abi_tag)
if not os:
return None
logger.debug("abi tag: %s earliest compatible kernel: %d.%d.%d", os, kmajor, kminor, kpatch)
return ABITag(os, kmajor, kminor, kpatch)
class SHNote:
def __init__(self, endian: str, buf: bytes):
self.endian = endian
self.buf = buf
# these will be initialized in `_parse()`
self.type_: int
self.descsz: int
self.name: str
self._parse()
def _parse(self):
namesz, self.descsz, self.type_ = struct.unpack_from(self.endian + "III", self.buf, 0x0)
name_offset = 0xC
self.desc_offset = name_offset + align(namesz, 0x4)
logger.debug("sh:namesz: 0x%02x descsz: 0x%02x type: 0x%04x", namesz, self.descsz, self.type_)
name_buf = self.buf[name_offset : name_offset + namesz]
self.name = read_cstr(name_buf, 0x0)
logger.debug("sh:name: %s", self.name)
@property
def abi_tag(self) -> Optional[ABITag]:
if self.name != "GNU":
return None
if self.descsz < 16:
return None
desc = self.buf[self.desc_offset : self.desc_offset + self.descsz]
abi_tag, kmajor, kminor, kpatch = struct.unpack_from(self.endian + "IIII", desc, 0x0)
logger.debug("GNU_ABI_TAG: 0x%02x", abi_tag)
os = GNU_ABI_TAG.get(abi_tag)
if not os:
return None
logger.debug("abi tag: %s earliest compatible kernel: %d.%d.%d", os, kmajor, kminor, kpatch)
return ABITag(os, kmajor, kminor, kpatch)
def guess_os_from_osabi(elf) -> Optional[OS]:
return elf.ei_osabi
def guess_os_from_ph_notes(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
for phdr in elf.program_headers:
if phdr.type != PT_NOTE:
continue
note = PHNote(elf.endian, phdr.buf)
if note.type_ != 1:
# > The type field shall be 1.
# Linux Standard Base Specification 1.2
# ref: https://refspecs.linuxfoundation.org/LSB_1.2.0/gLSB/noteabitag.html
continue
if note.name == "Linux":
logger.debug("note owner: %s", "LINUX")
return OS.LINUX
elif note.name == "OpenBSD":
logger.debug("note owner: %s", "OPENBSD")
return OS.OPENBSD
elif note.name == "NetBSD":
logger.debug("note owner: %s", "NETBSD")
return OS.NETBSD
elif note.name == "FreeBSD":
logger.debug("note owner: %s", "FREEBSD")
return OS.FREEBSD
elif note.name == "GNU":
abi_tag = note.abi_tag
if abi_tag:
return abi_tag.os
else:
# cannot make a guess about the OS, but probably linux or hurd
pass
return None
def guess_os_from_sh_notes(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
for shdr in elf.section_headers:
if shdr.type != SHT_NOTE:
continue
note = SHNote(elf.endian, shdr.buf)
if note.name == "Linux":
logger.debug("note owner: %s", "LINUX")
return OS.LINUX
elif note.name == "OpenBSD":
logger.debug("note owner: %s", "OPENBSD")
return OS.OPENBSD
elif note.name == "NetBSD":
logger.debug("note owner: %s", "NETBSD")
return OS.NETBSD
elif note.name == "FreeBSD":
logger.debug("note owner: %s", "FREEBSD")
return OS.FREEBSD
elif note.name == "GNU":
abi_tag = note.abi_tag
if abi_tag:
return abi_tag.os
else:
# cannot make a guess about the OS, but probably linux or hurd
pass
return None
def guess_os_from_linker(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
if linker and "ld-linux" in elf.linker:
return OS.LINUX
return None
def guess_os_from_abi_versions_needed(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()))):
# there are any GLIBC versions needed
if elf.e_machine != "i386":
# GLIBC runs on Linux and Hurd.
# for Hurd, its *only* on i386.
# so if we're not on i386, then we're on Linux.
return OS.LINUX
else:
# we're on i386, so we could be on either Linux or Hurd.
linker = elf.linker
if linker and "ld-linux" in linker:
return OS.LINUX
elif linker and "/ld.so" in linker:
return OS.HURD
else:
# we don't have any good guesses based on versions needed
pass
return None
def guess_os_from_needed_dependencies(elf) -> Optional[OS]:
for needed in elf.needed:
if needed.startswith("libmachuser.so"):
return OS.HURD
if needed.startswith("libhurduser.so"):
return OS.HURD
return None
def detect_elf_os(f) -> str:
"""
f: type Union[BinaryIO, IDAIO]
"""
elf = ELF(f)
osabi_guess = guess_os_from_osabi(elf)
logger.debug("guess: osabi: %s", osabi_guess)
ph_notes_guess = guess_os_from_ph_notes(elf)
logger.debug("guess: ph notes: %s", ph_notes_guess)
sh_notes_guess = guess_os_from_sh_notes(elf)
logger.debug("guess: sh notes: %s", sh_notes_guess)
linker_guess = guess_os_from_linker(elf)
logger.debug("guess: linker: %s", linker_guess)
abi_versions_needed_guess = guess_os_from_abi_versions_needed(elf)
logger.debug("guess: ABI versions needed: %s", abi_versions_needed_guess)
needed_dependencies_guess = guess_os_from_needed_dependencies(elf)
logger.debug("guess: needed dependencies: %s", needed_dependencies_guess)
ret = None
if osabi_guess:
ret = osabi_guess
elif ph_notes_guess:
ret = ph_notes_guess
elif sh_notes_guess:
ret = sh_notes_guess
elif linker_guess:
ret = linker_guess
elif abi_versions_needed_guess:
ret = abi_versions_needed_guess
elif needed_dependencies_guess:
ret = needed_dependencies_guess
return ret.value if ret is not None else "unknown"
def detect_elf_arch(f: BinaryIO) -> str:
return ELF(f).e_machine or "unknown"

View File

@@ -0,0 +1,158 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# Unless required by applicable law or agreed to in writing, software distributed under the License
# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and limitations under the License.
import io
import logging
from typing import Tuple, Iterator
from elftools.elf.elffile import ELFFile, SymbolTableSection
import capa.features.extractors.common
from capa.features.file import 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
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:
if not isinstance(section, SymbolTableSection):
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_symbols())
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)
def extract_file_section_names(elf, **kwargs):
for section in elf.iter_sections():
if section.name:
yield Section(section.name), AbsoluteVirtualAddress(section.header.sh_addr)
elif section.is_null():
yield Section("NULL"), AbsoluteVirtualAddress(section.header.sh_addr)
def extract_file_strings(buf, **kwargs):
yield from capa.features.extractors.common.extract_file_strings(buf)
def extract_file_os(elf, 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:
os_tuple = next(capa.features.extractors.common.extract_os(buf))
yield os_tuple
except StopIteration:
yield OS("unknown"), NO_ADDRESS
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()
arch = elf.get_machine_arch()
if arch == "x86":
yield Arch("i386"), NO_ADDRESS
elif arch == "x64":
yield Arch("amd64"), NO_ADDRESS
else:
logger.warning("unsupported architecture: %s", arch)
def extract_file_features(elf: ELFFile, buf: bytes) -> Iterator[Tuple[Feature, int]]:
for file_handler in FILE_HANDLERS:
for feature, addr in file_handler(elf=elf, buf=buf): # type: ignore
yield feature, addr
FILE_HANDLERS = (
# TODO extract_file_export_names,
extract_file_import_names,
extract_file_section_names,
extract_file_strings,
# no library matching
extract_file_format,
)
def extract_global_features(elf: ELFFile, buf: bytes) -> Iterator[Tuple[Feature, int]]:
for global_handler in GLOBAL_HANDLERS:
for feature, addr in global_handler(elf=elf, buf=buf): # type: ignore
yield feature, addr
GLOBAL_HANDLERS = (
extract_file_os,
extract_file_arch,
)
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()))
def get_base_address(self):
# virtual address of the first segment with type LOAD
for segment in self.elf.iter_segments():
if segment.header.p_type == "PT_LOAD":
return AbsoluteVirtualAddress(segment.header.p_vaddr)
def extract_global_features(self):
with open(self.path, "rb") as f:
buf = f.read()
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()
for feature, addr in extract_file_features(self.elf, buf):
yield feature, addr
def get_functions(self):
raise NotImplementedError("ElfFeatureExtractor can only be used to extract file features")
def extract_function_features(self, f):
raise NotImplementedError("ElfFeatureExtractor can only be used to extract file features")
def get_basic_blocks(self, f):
raise NotImplementedError("ElfFeatureExtractor can only be used to extract file features")
def extract_basic_block_features(self, f, bb):
raise NotImplementedError("ElfFeatureExtractor can only be used to extract file features")
def get_instructions(self, f, bb):
raise NotImplementedError("ElfFeatureExtractor can only be used to extract file features")
def extract_insn_features(self, f, bb, insn):
raise NotImplementedError("ElfFeatureExtractor can only be used to extract file features")
def is_library_function(self, addr):
raise NotImplementedError("ElfFeatureExtractor can only be used to extract file features")
def get_function_name(self, addr):
raise NotImplementedError("ElfFeatureExtractor can only be used to extract file features")

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
@@ -6,23 +6,18 @@
# 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 struct
import builtins
from capa.features.file import Import
from capa.features.insn import API
from typing import Tuple, Iterator
MIN_STACKSTRING_LEN = 8
def xor_static(data, i):
if sys.version_info >= (3, 0):
return bytes(c ^ i for c in data)
else:
return "".join(chr(ord(c) ^ i) for c in data)
def xor_static(data: bytes, i: int) -> bytes:
return bytes(c ^ i for c in data)
def is_aw_function(symbol):
def is_aw_function(symbol: str) -> bool:
"""
is the given function name an A/W function?
these are variants of functions that, on Windows, accept either a narrow or wide string.
@@ -34,18 +29,19 @@ def is_aw_function(symbol):
if symbol[-1] not in ("A", "W"):
return False
# second to last character should be lowercase letter
return "a" <= symbol[-2] <= "z" or "0" <= symbol[-2] <= "9"
return True
def is_ordinal(symbol):
def is_ordinal(symbol: str) -> bool:
"""
is the given symbol an ordinal that is prefixed by "#"?
"""
return symbol[0] == "#"
if symbol:
return symbol[0] == "#"
return False
def generate_symbols(dll, symbol):
def generate_symbols(dll: str, symbol: str) -> Iterator[str]:
"""
for a given dll and symbol name, generate variants.
we over-generate features to make matching easier.
@@ -55,6 +51,9 @@ def generate_symbols(dll, symbol):
- CreateFileA
- CreateFile
"""
# normalize dll name
dll = dll.lower()
# kernel32.CreateFileA
yield "%s.%s" % (dll, symbol)
@@ -71,11 +70,11 @@ def generate_symbols(dll, symbol):
yield symbol[:-1]
def all_zeros(bytez):
def all_zeros(bytez: bytes) -> bool:
return all(b == 0 for b in builtins.bytes(bytez))
def twos_complement(val, bits):
def twos_complement(val: int, bits: int) -> int:
"""
compute the 2's complement of int value val
@@ -88,3 +87,48 @@ def twos_complement(val, bits):
else:
# return positive value as is
return val
def carve_pe(pbytes: bytes, offset: int = 0) -> Iterator[Tuple[int, int]]:
"""
Generate (offset, key) tuples of embedded PEs
Based on the version from vivisect:
https://github.com/vivisect/vivisect/blob/7be4037b1cecc4551b397f840405a1fc606f9b53/PE/carve.py#L19
And its IDA adaptation:
capa/features/extractors/ida/file.py
"""
mz_xor = [
(
xor_static(b"MZ", key),
xor_static(b"PE", key),
key,
)
for key in range(256)
]
pblen = len(pbytes)
todo = [(pbytes.find(mzx, offset), mzx, pex, key) for mzx, pex, key in mz_xor]
todo = [(off, mzx, pex, key) for (off, mzx, pex, key) in todo if off != -1]
while len(todo):
off, mzx, pex, key = todo.pop()
# The MZ header has one field we will check
# e_lfanew is at 0x3c
e_lfanew = off + 0x3C
if pblen < (e_lfanew + 4):
continue
newoff = struct.unpack("<I", xor_static(pbytes[e_lfanew : e_lfanew + 4], key))[0]
nextres = pbytes.find(mzx, off + 1)
if nextres != -1:
todo.append((nextres, mzx, pex, key))
peoff = off + newoff
if pblen < (peoff + 2):
continue
if pbytes[peoff : peoff + 2] == pex:
yield (off, key)

View File

@@ -1,93 +0,0 @@
# Copyright (C) 2020 FireEye, 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 types
import idaapi
import capa.features.extractors.ida.file
import capa.features.extractors.ida.insn
import capa.features.extractors.ida.function
import capa.features.extractors.ida.basicblock
from capa.features.extractors import FeatureExtractor
def get_ea(self):
""" """
if isinstance(self, (idaapi.BasicBlock, idaapi.func_t)):
return self.start_ea
if isinstance(self, idaapi.insn_t):
return self.ea
raise TypeError
def add_ea_int_cast(o):
"""
dynamically add a cast-to-int (`__int__`) method to the given object
that returns the value of the `.ea` property.
this bit of skullduggery lets use cast viv-utils objects as ints.
the correct way of doing this is to update viv-utils (or subclass the objects here).
"""
if sys.version_info[0] >= 3:
setattr(o, "__int__", types.MethodType(get_ea, o))
else:
setattr(o, "__int__", types.MethodType(get_ea, o, type(o)))
return o
class IdaFeatureExtractor(FeatureExtractor):
def __init__(self):
super(IdaFeatureExtractor, self).__init__()
def get_base_address(self):
return idaapi.get_imagebase()
def extract_file_features(self):
for (feature, ea) in capa.features.extractors.ida.file.extract_features():
yield feature, ea
def get_functions(self):
import capa.features.extractors.ida.helpers as ida_helpers
# data structure shared across functions yielded here.
# useful for caching analysis relevant across a single workspace.
ctx = {}
# ignore library functions and thunk functions as identified by IDA
for f in ida_helpers.get_functions(skip_thunks=True, skip_libs=True):
setattr(f, "ctx", ctx)
yield add_ea_int_cast(f)
@staticmethod
def get_function(ea):
f = idaapi.get_func(ea)
setattr(f, "ctx", {})
return add_ea_int_cast(f)
def extract_function_features(self, f):
for (feature, ea) in capa.features.extractors.ida.function.extract_features(f):
yield feature, ea
def get_basic_blocks(self, f):
for bb in capa.features.extractors.ida.helpers.get_function_blocks(f):
yield add_ea_int_cast(bb)
def extract_basic_block_features(self, f, bb):
for (feature, ea) in capa.features.extractors.ida.basicblock.extract_features(f, bb):
yield feature, ea
def get_instructions(self, f, bb):
import capa.features.extractors.ida.helpers as ida_helpers
for insn in ida_helpers.get_instructions_in_range(bb.start_ea, bb.end_ea):
yield add_ea_int_cast(insn)
def extract_insn_features(self, f, bb, insn):
for (feature, ea) in capa.features.extractors.ida.insn.extract_features(f, bb, insn):
yield feature, ea

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
@@ -6,25 +6,23 @@
# 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 string
import struct
from typing import Tuple, Iterator
import idaapi
import capa.features.extractors.ida.helpers
from capa.features import Characteristic
from capa.features.common import Feature, Characteristic
from capa.features.address import Address
from capa.features.basicblock import BasicBlock
from capa.features.extractors.ida import helpers
from capa.features.extractors.helpers import MIN_STACKSTRING_LEN
from capa.features.extractors.base_extractor import BBHandle, FunctionHandle
def get_printable_len(op):
"""Return string length if all operand bytes are ascii or utf16-le printable
args:
op (IDA op_t)
"""
def get_printable_len(op: idaapi.op_t) -> int:
"""Return string length if all operand bytes are ascii or utf16-le printable"""
op_val = capa.features.extractors.ida.helpers.mask_op_val(op)
if op.dtype == idaapi.dt_byte:
@@ -38,19 +36,12 @@ def get_printable_len(op):
else:
raise ValueError("Unhandled operand data type 0x%x." % op.dtype)
def is_printable_ascii(chars):
if sys.version_info[0] >= 3:
return all(c < 127 and chr(c) in string.printable for c in chars)
else:
return all(ord(c) < 127 and c in string.printable for c in chars)
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):
if sys.version_info[0] >= 3:
if all(c == 0x00 for c in chars[1::2]):
return is_printable_ascii(chars[::2])
else:
if all(c == "\x00" for c in chars[1::2]):
return is_printable_ascii(chars[::2])
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 idaapi.get_dtype_size(op.dtype)
@@ -61,12 +52,8 @@ def get_printable_len(op):
return 0
def is_mov_imm_to_stack(insn):
"""verify instruction moves immediate onto stack
args:
insn (IDA insn_t)
"""
def is_mov_imm_to_stack(insn: idaapi.insn_t) -> bool:
"""verify instruction moves immediate onto stack"""
if insn.Op2.type != idaapi.o_imm:
return False
@@ -79,14 +66,10 @@ def is_mov_imm_to_stack(insn):
return True
def bb_contains_stackstring(f, bb):
def bb_contains_stackstring(f: idaapi.func_t, bb: idaapi.BasicBlock) -> bool:
"""check basic block for stackstring indicators
true if basic block contains enough moves of constant bytes to the stack
args:
f (IDA func_t)
bb (IDA BasicBlock)
"""
count = 0
for insn in capa.features.extractors.ida.helpers.get_instructions_in_range(bb.start_ea, bb.end_ea):
@@ -97,39 +80,24 @@ def bb_contains_stackstring(f, bb):
return False
def extract_bb_stackstring(f, bb):
"""extract stackstring indicators from basic block
args:
f (IDA func_t)
bb (IDA BasicBlock)
"""
if bb_contains_stackstring(f, bb):
yield Characteristic("stack string"), bb.start_ea
def extract_bb_stackstring(fh: FunctionHandle, bbh: BBHandle) -> Iterator[Tuple[Feature, Address]]:
"""extract stackstring indicators from basic block"""
if bb_contains_stackstring(fh.inner, bbh.inner):
yield Characteristic("stack string"), bbh.address
def extract_bb_tight_loop(f, bb):
"""extract tight loop indicators from a basic block
args:
f (IDA func_t)
bb (IDA BasicBlock)
"""
if capa.features.extractors.ida.helpers.is_basic_block_tight_loop(bb):
yield Characteristic("tight loop"), bb.start_ea
def extract_bb_tight_loop(fh: FunctionHandle, bbh: BBHandle) -> Iterator[Tuple[Feature, Address]]:
"""extract tight loop indicators from a basic block"""
if capa.features.extractors.ida.helpers.is_basic_block_tight_loop(bbh.inner):
yield Characteristic("tight loop"), bbh.address
def extract_features(f, bb):
"""extract basic block features
args:
f (IDA func_t)
bb (IDA BasicBlock)
"""
def extract_features(fh: FunctionHandle, bbh: BBHandle) -> Iterator[Tuple[Feature, Address]]:
"""extract basic block features"""
for bb_handler in BASIC_BLOCK_HANDLERS:
for (feature, ea) in bb_handler(f, bb):
yield feature, ea
yield BasicBlock(), bb.start_ea
for feature, addr in bb_handler(fh, bbh):
yield feature, addr
yield BasicBlock(), bbh.address
BASIC_BLOCK_HANDLERS = (
@@ -140,9 +108,10 @@ BASIC_BLOCK_HANDLERS = (
def main():
features = []
for f in helpers.get_functions(skip_thunks=True, skip_libs=True):
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(f, bb)))
features.extend(list(extract_features(fhandle, bb)))
import pprint

View File

@@ -0,0 +1,71 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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 idaapi
import capa.ida.helpers
import capa.features.extractors.elf
import capa.features.extractors.ida.file
import capa.features.extractors.ida.insn
import capa.features.extractors.ida.global_
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
class IdaFeatureExtractor(FeatureExtractor):
def __init__(self):
super().__init__()
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())
self.global_features.extend(capa.features.extractors.ida.global_.extract_arch())
def get_base_address(self):
return AbsoluteVirtualAddress(idaapi.get_imagebase())
def extract_global_features(self):
yield from self.global_features
def extract_file_features(self):
yield from capa.features.extractors.ida.file.extract_features()
def get_functions(self) -> Iterator[FunctionHandle]:
import capa.features.extractors.ida.helpers as ida_helpers
# ignore library functions and thunk functions as identified by IDA
yield from ida_helpers.get_functions(skip_thunks=True, skip_libs=True)
@staticmethod
def get_function(ea: int) -> FunctionHandle:
f = idaapi.get_func(ea)
return FunctionHandle(address=AbsoluteVirtualAddress(f.start_ea), inner=f)
def extract_function_features(self, fh: FunctionHandle) -> Iterator[Tuple[Feature, Address]]:
yield from capa.features.extractors.ida.function.extract_features(fh)
def get_basic_blocks(self, fh: FunctionHandle) -> Iterator[BBHandle]:
import capa.features.extractors.ida.helpers as ida_helpers
for bb in ida_helpers.get_function_blocks(fh.inner):
yield BBHandle(address=AbsoluteVirtualAddress(bb.start_ea), inner=bb)
def extract_basic_block_features(self, fh: FunctionHandle, bbh: BBHandle) -> Iterator[Tuple[Feature, Address]]:
yield from capa.features.extractors.ida.basicblock.extract_features(fh, bbh)
def get_instructions(self, fh: FunctionHandle, bbh: BBHandle) -> Iterator[InsnHandle]:
import capa.features.extractors.ida.helpers as ida_helpers
for insn in ida_helpers.get_instructions_in_range(bbh.inner.start_ea, bbh.inner.end_ea):
yield InsnHandle(address=AbsoluteVirtualAddress(insn.ea), inner=insn)
def extract_insn_features(self, fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle):
yield from capa.features.extractors.ida.insn.extract_features(fh, bbh, ih)

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
@@ -7,26 +7,26 @@
# See the License for the specific language governing permissions and limitations under the License.
import struct
from typing import Tuple, Iterator
import idc
import idaapi
import idautils
import capa.features.extractors.common
import capa.features.extractors.helpers
import capa.features.extractors.strings
import capa.features.extractors.ida.helpers
from capa.features import String, Characteristic
from capa.features.file import Export, Import, Section
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
def check_segment_for_pe(seg):
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
args:
seg (IDA segment_t)
"""
seg_max = seg.end_ea
mz_xor = [
@@ -37,11 +37,11 @@ def check_segment_for_pe(seg):
)
for i in range(256)
]
todo = [
(capa.features.extractors.ida.helpers.find_byte_sequence(seg.start_ea, seg.end_ea, mzx), mzx, pex, i)
for mzx, pex, i in mz_xor
]
todo = [(off, mzx, pex, i) for (off, mzx, pex, i) in todo if off != idaapi.BADADDR]
todo = []
for mzx, pex, i in mz_xor:
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()
@@ -59,14 +59,13 @@ def check_segment_for_pe(seg):
continue
if idc.get_bytes(peoff, 2) == pex:
yield (off, i)
yield off, i
nextres = capa.features.extractors.ida.helpers.find_byte_sequence(off + 1, seg.end_ea, mzx)
if nextres != -1:
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():
def extract_file_embedded_pe() -> Iterator[Tuple[Feature, Address]]:
"""extract embedded PE features
IDA must load resource sections for this to be complete
@@ -74,17 +73,17 @@ def extract_file_embedded_pe():
- Check 'Load resource sections' when opening binary in IDA manually
"""
for seg in capa.features.extractors.ida.helpers.get_segments(skip_header_segments=True):
for (ea, _) in check_segment_for_pe(seg):
yield Characteristic("embedded pe"), ea
for ea, _ in check_segment_for_pe(seg):
yield Characteristic("embedded pe"), FileOffsetAddress(ea)
def extract_file_export_names():
""" extract function exports """
for (_, _, ea, name) in idautils.Entries():
yield Export(name), ea
def extract_file_export_names() -> Iterator[Tuple[Feature, Address]]:
"""extract function exports"""
for _, _, ea, name in idautils.Entries():
yield Export(name), AbsoluteVirtualAddress(ea)
def extract_file_import_names():
def extract_file_import_names() -> Iterator[Tuple[Feature, Address]]:
"""extract function imports
1. imports by ordinal:
@@ -95,8 +94,16 @@ def extract_file_import_names():
- modulename.importname
- importname
"""
for (ea, info) in capa.features.extractors.ida.helpers.get_file_imports().items():
if info[1]:
for ea, info in capa.features.extractors.ida.helpers.get_file_imports().items():
addr = AbsoluteVirtualAddress(ea)
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]):
yield Import(name), addr
dll = info[0]
symbol = "#%d" % (info[2])
elif info[1]:
dll = info[0]
symbol = info[1]
elif info[2]:
@@ -106,10 +113,13 @@ def extract_file_import_names():
continue
for name in capa.features.extractors.helpers.generate_symbols(dll, symbol):
yield Import(name), ea
yield Import(name), addr
for ea, info in capa.features.extractors.ida.helpers.get_file_externs().items():
yield Import(info[1]), AbsoluteVirtualAddress(ea)
def extract_file_section_names():
def extract_file_section_names() -> Iterator[Tuple[Feature, Address]]:
"""extract section names
IDA must load resource sections for this to be complete
@@ -117,10 +127,10 @@ def extract_file_section_names():
- Check 'Load resource sections' when opening binary in IDA manually
"""
for seg in capa.features.extractors.ida.helpers.get_segments(skip_header_segments=True):
yield Section(idaapi.get_segm_name(seg)), seg.start_ea
yield Section(idaapi.get_segm_name(seg)), AbsoluteVirtualAddress(seg.start_ea)
def extract_file_strings():
def extract_file_strings() -> Iterator[Tuple[Feature, Address]]:
"""extract ASCII and UTF-16 LE strings
IDA must load resource sections for this to be complete
@@ -130,18 +140,50 @@ def extract_file_strings():
for seg in capa.features.extractors.ida.helpers.get_segments():
seg_buff = capa.features.extractors.ida.helpers.get_segment_buffer(seg)
# differing to common string extractor factor in segment offset here
for s in capa.features.extractors.strings.extract_ascii_strings(seg_buff):
yield String(s.s), (seg.start_ea + s.offset)
yield String(s.s), FileOffsetAddress(seg.start_ea + s.offset)
for s in capa.features.extractors.strings.extract_unicode_strings(seg_buff):
yield String(s.s), (seg.start_ea + s.offset)
yield String(s.s), FileOffsetAddress(seg.start_ea + s.offset)
def extract_features():
""" extract file features """
def extract_file_function_names() -> Iterator[Tuple[Feature, Address]]:
"""
extract the names of statically-linked library functions.
"""
for ea in idautils.Functions():
addr = AbsoluteVirtualAddress(ea)
if idaapi.get_func(ea).flags & idaapi.FUNC_LIB:
name = idaapi.get_name(ea)
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]]:
file_info = idaapi.get_inf_structure()
if file_info.filetype in (idaapi.f_PE, idaapi.f_COFF):
yield Format(FORMAT_PE), NO_ADDRESS
elif file_info.filetype == idaapi.f_ELF:
yield Format(FORMAT_ELF), NO_ADDRESS
elif file_info.filetype == idaapi.f_BIN:
# 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)
def extract_features() -> Iterator[Tuple[Feature, Address]]:
"""extract file features"""
for file_handler in FILE_HANDLERS:
for feature, va in file_handler():
yield feature, va
for feature, addr in file_handler():
yield feature, addr
FILE_HANDLERS = (
@@ -150,6 +192,8 @@ FILE_HANDLERS = (
extract_file_strings,
extract_file_section_names,
extract_file_embedded_pe,
extract_file_function_names,
extract_file_format,
)

View File

@@ -1,35 +1,31 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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 idaapi
import idautils
import capa.features.extractors.ida.helpers
from capa.features import Characteristic
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(f):
"""extract callers to a function
args:
f (IDA func_t)
"""
for ea in idautils.CodeRefsTo(f.start_ea, True):
yield Characteristic("calls to"), ea
def extract_function_calls_to(fh: FunctionHandle):
"""extract callers to a function"""
for ea in idautils.CodeRefsTo(fh.inner.start_ea, True):
yield Characteristic("calls to"), AbsoluteVirtualAddress(ea)
def extract_function_loop(f):
"""extract loop indicators from a function
args:
f (IDA func_t)
"""
def extract_function_loop(fh: FunctionHandle):
"""extract loop indicators from a function"""
f: idaapi.func_t = fh.inner
edges = []
# construct control flow graph
@@ -38,28 +34,19 @@ def extract_function_loop(f):
edges.append((bb.start_ea, succ.start_ea))
if loops.has_loop(edges):
yield Characteristic("loop"), f.start_ea
yield Characteristic("loop"), fh.address
def extract_recursive_call(f):
"""extract recursive function call
args:
f (IDA func_t)
"""
if capa.features.extractors.ida.helpers.is_function_recursive(f):
yield Characteristic("recursive call"), f.start_ea
def extract_recursive_call(fh: FunctionHandle):
"""extract recursive function call"""
if capa.features.extractors.ida.helpers.is_function_recursive(fh.inner):
yield Characteristic("recursive call"), fh.address
def extract_features(f):
"""extract function features
arg:
f (IDA func_t)
"""
def extract_features(fh: FunctionHandle) -> Iterator[Tuple[Feature, Address]]:
for func_handler in FUNCTION_HANDLERS:
for (feature, ea) in func_handler(f):
yield feature, ea
for feature, addr in func_handler(fh):
yield feature, addr
FUNCTION_HANDLERS = (extract_function_calls_to, extract_function_loop, extract_recursive_call)
@@ -68,8 +55,8 @@ FUNCTION_HANDLERS = (extract_function_calls_to, extract_function_loop, extract_r
def main():
""" """
features = []
for f in capa.features.extractors.ida.get_functions(skip_thunks=True, skip_libs=True):
features.extend(list(extract_features(f)))
for fhandle in capa.features.extractors.ida.helpers.get_functions(skip_thunks=True, skip_libs=True):
features.extend(list(extract_features(fhandle)))
import pprint

View File

@@ -0,0 +1,58 @@
import logging
import contextlib
from typing import Tuple, Iterator
import idaapi
import ida_loader
import capa.ida.helpers
import capa.features.extractors.elf
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 = ida_loader.get_file_type_name()
if "PE" in format_name:
yield OS(OS_WINDOWS), NO_ADDRESS
elif "ELF" in format_name:
with contextlib.closing(capa.ida.helpers.IDAIO()) 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]]:
info: idaapi.idainfo = idaapi.get_inf_structure()
if info.procname == "metapc" and info.is_64bit():
yield Arch(ARCH_AMD64), NO_ADDRESS
elif info.procname == "metapc" and info.is_32bit():
yield Arch(ARCH_I386), NO_ADDRESS
elif info.procname == "metapc":
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", info.procname)
return

View File

@@ -1,51 +1,56 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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 string
from typing import Any, Dict, Tuple, Iterator, Optional
import idc
import idaapi
import idautils
import ida_bytes
import ida_segment
from capa.features.address import AbsoluteVirtualAddress
from capa.features.extractors.base_extractor import FunctionHandle
def find_byte_sequence(start, end, seq):
"""find byte sequence
def find_byte_sequence(start: int, end: int, seq: bytes) -> Iterator[int]:
"""yield all ea of a given byte sequence
args:
start: min virtual address
end: max virtual address
seq: bytes to search e.g. b'\x01\x03'
seq: bytes to search e.g. b"\x01\x03"
"""
if sys.version_info[0] >= 3:
return idaapi.find_binary(start, end, " ".join(["%02x" % b for b in seq]), 0, idaapi.SEARCH_DOWN)
else:
return idaapi.find_binary(start, end, " ".join(["%02x" % ord(b) for b in seq]), 0, idaapi.SEARCH_DOWN)
seqstr = " ".join(["%02x" % b for b in seq])
while True:
# TODO find_binary: Deprecated. Please use ida_bytes.bin_search() instead.
ea = idaapi.find_binary(start, end, seqstr, 0, idaapi.SEARCH_DOWN)
if ea == idaapi.BADADDR:
break
start = ea + 1
yield ea
def get_functions(start=None, end=None, skip_thunks=False, skip_libs=False):
def get_functions(
start: Optional[int] = None, end: Optional[int] = None, skip_thunks: bool = False, skip_libs: bool = False
) -> Iterator[FunctionHandle]:
"""get functions, range optional
args:
start: min virtual address
end: max virtual address
ret:
yield func_t*
"""
for ea in idautils.Functions(start=start, end=end):
f = idaapi.get_func(ea)
if not (skip_thunks and (f.flags & idaapi.FUNC_THUNK) or skip_libs and (f.flags & idaapi.FUNC_LIB)):
yield f
yield FunctionHandle(address=AbsoluteVirtualAddress(ea), inner=f)
def get_segments(skip_header_segments=False):
def get_segments(skip_header_segments=False) -> Iterator[idaapi.segment_t]:
"""get list of segments (sections) in the binary image
args:
@@ -57,7 +62,7 @@ def get_segments(skip_header_segments=False):
yield seg
def get_segment_buffer(seg):
def get_segment_buffer(seg: idaapi.segment_t) -> bytes:
"""return bytes stored in a given segment
decrease buffer size until IDA is able to read bytes from the segment
@@ -75,8 +80,8 @@ def get_segment_buffer(seg):
return buff if buff else b""
def get_file_imports():
""" get file imports """
def get_file_imports() -> Dict[int, Tuple[str, str, int]]:
"""get file imports"""
imports = {}
for idx in range(idaapi.get_import_module_qty()):
@@ -85,10 +90,18 @@ def get_file_imports():
if not library:
continue
# IDA uses section names for the library of ELF imports, like ".dynsym"
library = library.lstrip(".")
def inspect_import(ea, function, ordinal):
if function and function.startswith("__imp_"):
# handle mangled names starting
# 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
@@ -97,14 +110,25 @@ def get_file_imports():
return imports
def get_instructions_in_range(start, end):
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):
continue
for ea in idautils.Functions(seg.start_ea, seg.end_ea):
externs[ea] = ("", idaapi.get_func_name(ea), -1)
return externs
def get_instructions_in_range(start: int, end: int) -> Iterator[idaapi.insn_t]:
"""yield instructions in range
args:
start: virtual address (inclusive)
end: virtual address (exclusive)
yield:
(insn_t*)
"""
for head in idautils.Heads(start, end):
insn = idautils.DecodeInstruction(head)
@@ -112,8 +136,8 @@ def get_instructions_in_range(start, end):
yield insn
def is_operand_equal(op1, op2):
""" compare two IDA op_t """
def is_operand_equal(op1: idaapi.op_t, op2: idaapi.op_t) -> bool:
"""compare two IDA op_t"""
if op1.flags != op2.flags:
return False
@@ -138,8 +162,8 @@ def is_operand_equal(op1, op2):
return True
def is_basic_block_equal(bb1, bb2):
""" compare two IDA BasicBlock """
def is_basic_block_equal(bb1: idaapi.BasicBlock, bb2: idaapi.BasicBlock) -> bool:
"""compare two IDA BasicBlock"""
if bb1.start_ea != bb2.start_ea:
return False
@@ -152,13 +176,17 @@ def is_basic_block_equal(bb1, bb2):
return True
def basic_block_size(bb):
""" calculate size of basic block """
def basic_block_size(bb: idaapi.BasicBlock) -> int:
"""calculate size of basic block"""
return bb.end_ea - bb.start_ea
def read_bytes_at(ea, count):
def read_bytes_at(ea: int, count: int) -> bytes:
""" """
# check if byte has a value, see get_wide_byte doc
if not idc.is_loaded(ea):
return b""
segm_end = idc.get_segm_end(ea)
if ea + count > segm_end:
return idc.get_bytes(ea, segm_end - ea)
@@ -166,10 +194,10 @@ def read_bytes_at(ea, count):
return idc.get_bytes(ea, count)
def find_string_at(ea, min=4):
""" check if ASCII string exists at a given virtual address """
def find_string_at(ea: int, min_: int = 4) -> str:
"""check if ASCII string exists at a given virtual address"""
found = idaapi.get_strlit_contents(ea, -1, idaapi.STRTYPE_C)
if found and len(found) > min:
if found and len(found) >= min_:
try:
found = found.decode("ascii")
# hacky check for IDA bug; get_strlit_contents also reads Unicode as
@@ -183,7 +211,7 @@ def find_string_at(ea, min=4):
return ""
def get_op_phrase_info(op):
def get_op_phrase_info(op: idaapi.op_t) -> Dict:
"""parse phrase features from operand
Pretty much dup of sark's implementation:
@@ -193,7 +221,8 @@ def get_op_phrase_info(op):
return {}
scale = 1 << ((op.specflag2 & 0xC0) >> 6)
offset = op.addr
# IDA ea_t may be 32- or 64-bit; we assume displacement can only be 32-bit
offset = op.addr & 0xFFFFFFFF
if op.specflag1 == 0:
index = None
@@ -220,24 +249,24 @@ def get_op_phrase_info(op):
return {"base": base, "index": index, "scale": scale, "offset": offset}
def is_op_write(insn, op):
""" Check if an operand is written to (destination operand) """
def is_op_write(insn: idaapi.insn_t, op: idaapi.op_t) -> bool:
"""Check if an operand is written to (destination operand)"""
return idaapi.has_cf_chg(insn.get_canon_feature(), op.n)
def is_op_read(insn, op):
""" Check if an operand is read from (source operand) """
def is_op_read(insn: idaapi.insn_t, op: idaapi.op_t) -> bool:
"""Check if an operand is read from (source operand)"""
return idaapi.has_cf_use(insn.get_canon_feature(), op.n)
def is_op_offset(insn, op):
""" Check is an operand has been marked as an offset (by auto-analysis or manually) """
def is_op_offset(insn: idaapi.insn_t, op: idaapi.op_t) -> bool:
"""Check is an operand has been marked as an offset (by auto-analysis or manually)"""
flags = idaapi.get_flags(insn.ea)
return ida_bytes.is_off(flags, op.n)
def is_sp_modified(insn):
""" determine if instruction modifies SP, ESP, RSP """
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
@@ -245,8 +274,8 @@ def is_sp_modified(insn):
return False
def is_bp_modified(insn):
""" check if instruction modifies BP, EBP, RBP """
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
@@ -254,13 +283,13 @@ def is_bp_modified(insn):
return False
def is_frame_register(reg):
""" check if register is sp or bp """
def is_frame_register(reg: int) -> bool:
"""check if register is sp or bp"""
return reg in (idautils.procregs.sp.reg, idautils.procregs.bp.reg)
def get_insn_ops(insn, target_ops=()):
""" yield op_t for instruction, filter on type if specified """
def get_insn_ops(insn: idaapi.insn_t, target_ops: Optional[Tuple[Any]] = None) -> idaapi.op_t:
"""yield op_t for instruction, filter on type if specified"""
for op in insn.ops:
if op.type == idaapi.o_void:
# avoid looping all 6 ops if only subset exists
@@ -270,12 +299,12 @@ def get_insn_ops(insn, target_ops=()):
yield op
def is_op_stack_var(ea, index):
""" check if operand is a stack variable """
def is_op_stack_var(ea: int, index: int) -> bool:
"""check if operand is a stack variable"""
return idaapi.is_stkvar(idaapi.get_flags(ea), index)
def mask_op_val(op):
def mask_op_val(op: idaapi.op_t) -> int:
"""mask value by data type
necessary due to a bug in AMD64
@@ -295,26 +324,18 @@ def mask_op_val(op):
return masks.get(op.dtype, op.value) & op.value
def is_function_recursive(f):
"""check if function is recursive
args:
f (IDA func_t)
"""
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
def is_basic_block_tight_loop(bb):
def is_basic_block_tight_loop(bb: idaapi.BasicBlock) -> bool:
"""check basic block loops to self
true if last instruction in basic block branches to basic block start
args:
f (IDA func_t)
bb (IDA BasicBlock)
"""
bb_end = idc.prev_head(bb.end_ea)
if bb.start_ea < bb_end:
@@ -324,8 +345,8 @@ def is_basic_block_tight_loop(bb):
return False
def find_data_reference_from_insn(insn, max_depth=10):
""" search for data reference from instruction, return address of instruction if no reference exists """
def find_data_reference_from_insn(insn: idaapi.insn_t, max_depth: int = 10) -> int:
"""search for data reference from instruction, return address of instruction if no reference exists"""
depth = 0
ea = insn.ea
@@ -340,6 +361,10 @@ def find_data_reference_from_insn(insn, max_depth=10):
# break if circular reference
break
if not idaapi.is_mapped(data_refs[0]):
# break if address is not mapped
break
depth += 1
if depth > max_depth:
# break if max depth
@@ -350,19 +375,18 @@ def find_data_reference_from_insn(insn, max_depth=10):
return ea
def get_function_blocks(f):
"""yield basic blocks contained in specified function
args:
f (IDA func_t)
yield:
block (IDA BasicBlock)
"""
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
def is_basic_block_return(bb):
""" check if basic block is return block """
def is_basic_block_return(bb: idaapi.BasicBlock) -> bool:
"""check if basic block is return block"""
return bb.type == idaapi.fcb_ret
def has_sib(oper: idaapi.op_t) -> bool:
# via: https://reverseengineering.stackexchange.com/a/14300
return oper.specflag1 == 1

View File

@@ -1,10 +1,11 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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 idc
import idaapi
@@ -12,87 +13,112 @@ import idautils
import capa.features.extractors.helpers
import capa.features.extractors.ida.helpers
from capa.features import ARCH_X32, ARCH_X64, MAX_BYTES_FEATURE_SIZE, Bytes, String, Characteristic
from capa.features.insn import API, Number, Offset, Mnemonic
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.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
def get_arch(ctx):
"""
fetch the ARCH_* constant for the currently open workspace.
via Tamir Bahar/@tmr232
https://reverseengineering.stackexchange.com/a/11398/17194
"""
if "arch" not in ctx:
info = idaapi.get_inf_structure()
if info.is_64bit():
ctx["arch"] = ARCH_X64
elif info.is_32bit():
ctx["arch"] = ARCH_X32
else:
raise ValueError("unexpected architecture")
return ctx["arch"]
def get_imports(ctx):
def get_imports(ctx: Dict[str, Any]) -> Dict[int, Any]:
if "imports_cache" not in ctx:
ctx["imports_cache"] = capa.features.extractors.ida.helpers.get_file_imports()
return ctx["imports_cache"]
def check_for_api_call(ctx, insn):
""" check instruction for API call """
if not idaapi.is_call_insn(insn):
return
def get_externs(ctx: Dict[str, Any]) -> Dict[int, Any]:
if "externs_cache" not in ctx:
ctx["externs_cache"] = capa.features.extractors.ida.helpers.get_file_externs()
return ctx["externs_cache"]
for ref in idautils.CodeRefsFrom(insn.ea, False):
info = get_imports(ctx).get(ref, ())
def check_for_api_call(insn: idaapi.insn_t, funcs: Dict[int, Any]) -> Iterator[Any]:
"""check instruction for API call"""
info = ()
ref = insn.ea
# attempt to resolve API calls by following chained thunks to a reasonable depth
for _ in range(THUNK_CHAIN_DEPTH_DELTA):
# assume only one code/data ref when resolving "call" or "jmp"
try:
ref = tuple(idautils.CodeRefsFrom(ref, False))[0]
except IndexError:
try:
# thunks may be marked as data refs
ref = tuple(idautils.DataRefsFrom(ref))[0]
except IndexError:
break
info = funcs.get(ref, ())
if info:
yield "%s.%s" % (info[0], info[1])
else:
f = idaapi.get_func(ref)
# check if call to thunk
# TODO: first instruction might not always be the thunk
if f and (f.flags & idaapi.FUNC_THUNK):
for thunk_ref in idautils.DataRefsFrom(ref):
# TODO: always data ref for thunk??
info = get_imports(ctx).get(thunk_ref, ())
if info:
yield "%s.%s" % (info[0], info[1])
break
f = idaapi.get_func(ref)
if not f or not (f.flags & idaapi.FUNC_THUNK):
break
if info:
yield info
def extract_insn_api_features(f, bb, insn):
"""parse instruction API features
args:
f (IDA func_t)
bb (IDA BasicBlock)
insn (IDA insn_t)
def extract_insn_api_features(fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle) -> Iterator[Tuple[Feature, Address]]:
"""
parse instruction API features
example:
call dword [0x00473038]
call dword [0x00473038]
"""
for api in check_for_api_call(f.ctx, insn):
dll, _, symbol = api.rpartition(".")
for name in capa.features.extractors.helpers.generate_symbols(dll, symbol):
yield API(name), insn.ea
insn: idaapi.insn_t = ih.inner
if not insn.get_canon_mnem() in ("call", "jmp"):
return
# check calls to imported functions
for api in check_for_api_call(insn, get_imports(fh.ctx)):
# tuple (<module>, <function>, <ordinal>)
for name in capa.features.extractors.helpers.generate_symbols(api[0], api[1]):
yield API(name), ih.address
# check calls to extern functions
for api in check_for_api_call(insn, get_externs(fh.ctx)):
# tuple (<module>, <function>, <ordinal>)
yield API(api[1]), ih.address
# extract IDA/FLIRT recognized API functions
targets = tuple(idautils.CodeRefsFrom(insn.ea, False))
if not targets:
return
target = targets[0]
target_func = idaapi.get_func(target)
if not target_func or target_func.start_ea != target:
# not a function (start)
return
if target_func.flags & idaapi.FUNC_LIB:
name = idaapi.get_name(target_func.start_ea)
yield API(name), ih.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 API(name[1:]), ih.address
def extract_insn_number_features(f, bb, insn):
"""parse instruction number features
args:
f (IDA func_t)
bb (IDA BasicBlock)
insn (IDA insn_t)
def extract_insn_number_features(
fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle
) -> Iterator[Tuple[Feature, Address]]:
"""
parse instruction number features
example:
push 3136B0h ; dwControlCode
"""
insn: idaapi.insn_t = ih.inner
if idaapi.is_ret_insn(insn):
# skip things like:
# .text:0042250E retn 8
@@ -103,7 +129,11 @@ def extract_insn_number_features(f, bb, insn):
# .text:00401145 add esp, 0Ch
return
for op in capa.features.extractors.ida.helpers.get_insn_ops(insn, target_ops=(idaapi.o_imm, idaapi.o_mem)):
for i, op in enumerate(insn.ops):
if op.type == idaapi.o_void:
break
if op.type not in (idaapi.o_imm, idaapi.o_mem):
continue
# skip things like:
# .text:00401100 shr eax, offset loc_C
if capa.features.extractors.ida.helpers.is_op_offset(insn, op):
@@ -114,62 +144,82 @@ def extract_insn_number_features(f, bb, insn):
else:
const = op.addr
yield Number(const), insn.ea
yield Number(const, arch=get_arch(f.ctx)), insn.ea
yield Number(const), ih.address
yield OperandNumber(i, const), ih.address
if insn.itype == idaapi.NN_add and 0 < const < MAX_STRUCTURE_SIZE and op.type == idaapi.o_imm:
# for pattern like:
#
# add eax, 0x10
#
# assume 0x10 is also an offset (imagine eax is a pointer).
yield Offset(const), ih.address
yield OperandOffset(i, const), ih.address
def extract_insn_bytes_features(f, bb, insn):
"""parse referenced byte sequences
args:
f (IDA func_t)
bb (IDA BasicBlock)
insn (IDA insn_t)
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
"""
insn: idaapi.insn_t = ih.inner
if idaapi.is_call_insn(insn):
return
ref = capa.features.extractors.ida.helpers.find_data_reference_from_insn(insn)
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):
yield Bytes(extracted_bytes), insn.ea
if not capa.features.extractors.ida.helpers.find_string_at(insn.ea):
# don't extract byte features for obvious strings
yield Bytes(extracted_bytes), ih.address
def extract_insn_string_features(f, bb, insn):
"""parse instruction string features
args:
f (IDA func_t)
bb (IDA BasicBlock)
insn (IDA insn_t)
def extract_insn_string_features(
fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle
) -> Iterator[Tuple[Feature, Address]]:
"""
parse instruction string features
example:
push offset aAcr ; "ACR > "
"""
insn: idaapi.insn_t = ih.inner
ref = capa.features.extractors.ida.helpers.find_data_reference_from_insn(insn)
if ref != insn.ea:
found = capa.features.extractors.ida.helpers.find_string_at(ref)
if found:
yield String(found), insn.ea
yield String(found), ih.address
def extract_insn_offset_features(f, bb, insn):
"""parse instruction structure offset features
args:
f (IDA func_t)
bb (IDA BasicBlock)
insn (IDA insn_t)
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
"""
for op in capa.features.extractors.ida.helpers.get_insn_ops(insn, target_ops=(idaapi.o_phrase, idaapi.o_displ)):
insn: idaapi.insn_t = ih.inner
for i, op in enumerate(insn.ops):
if op.type == idaapi.o_void:
break
if op.type not in (idaapi.o_phrase, idaapi.o_displ):
continue
if capa.features.extractors.ida.helpers.is_op_stack_var(insn.ea, op.n):
continue
p_info = capa.features.extractors.ida.helpers.get_op_phrase_info(op)
op_off = p_info.get("offset", 0)
op_off = p_info.get("offset", None)
if op_off is None:
continue
if idaapi.is_mapped(op_off):
# Ignore:
# mov esi, dword_1005B148[esi]
@@ -180,12 +230,32 @@ def extract_insn_offset_features(f, bb, insn):
# https://stackoverflow.com/questions/31853189/x86-64-assembly-why-displacement-not-64-bits
op_off = capa.features.extractors.helpers.twos_complement(op_off, 32)
yield Offset(op_off), insn.ea
yield Offset(op_off, arch=get_arch(f.ctx)), insn.ea
yield Offset(op_off), ih.address
yield OperandOffset(i, op_off), ih.address
if (
insn.itype == idaapi.NN_lea
and i == 1
# o_displ is used for both:
# [eax+1]
# [eax+ebx+2]
and op.type == idaapi.o_displ
# but the SIB is only present for [eax+ebx+2]
# which we don't want
and not capa.features.extractors.ida.helpers.has_sib(op)
):
# for pattern like:
#
# lea eax, [ebx + 1]
#
# assume 1 is also an offset (imagine ebx is a zero register).
yield Number(op_off), ih.address
yield OperandNumber(i, op_off), ih.address
def contains_stack_cookie_keywords(s):
"""check if string contains stack cookie keywords
def contains_stack_cookie_keywords(s: str) -> bool:
"""
check if string contains stack cookie keywords
Examples:
xor ecx, ebp ; StackCookie
@@ -199,7 +269,7 @@ def contains_stack_cookie_keywords(s):
return any(keyword in s for keyword in ("stack", "security"))
def bb_stack_cookie_registers(bb):
def bb_stack_cookie_registers(bb: idaapi.BasicBlock) -> Iterator[int]:
"""scan basic block for stack cookie operations
yield registers ids that may have been used for stack cookie operations
@@ -233,8 +303,8 @@ def bb_stack_cookie_registers(bb):
yield op.reg
def is_nzxor_stack_cookie_delta(f, bb, insn):
""" check if nzxor exists within stack cookie delta """
def is_nzxor_stack_cookie_delta(f: idaapi.func_t, bb: idaapi.BasicBlock, insn: idaapi.insn_t) -> bool:
"""check if nzxor exists within stack cookie delta"""
# security cookie check should use SP or BP
if not capa.features.extractors.ida.helpers.is_frame_register(insn.Op2.reg):
return False
@@ -256,8 +326,8 @@ def is_nzxor_stack_cookie_delta(f, bb, insn):
return False
def is_nzxor_stack_cookie(f, bb, insn):
""" check if nzxor is related to stack cookie """
def is_nzxor_stack_cookie(f: idaapi.func_t, bb: idaapi.BasicBlock, insn: idaapi.insn_t) -> bool:
"""check if nzxor is related to stack cookie"""
if contains_stack_cookie_keywords(idaapi.get_cmt(insn.ea, False)):
# Example:
# xor ecx, ebp ; StackCookie
@@ -273,37 +343,49 @@ def is_nzxor_stack_cookie(f, bb, insn):
return False
def extract_insn_nzxor_characteristic_features(f, bb, insn):
"""parse instruction non-zeroing XOR instruction
ignore expected non-zeroing XORs, e.g. security cookies
args:
f (IDA func_t)
bb (IDA BasicBlock)
insn (IDA insn_t)
def extract_insn_nzxor_characteristic_features(
fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle
) -> Iterator[Tuple[Feature, Address]]:
"""
if insn.itype != idaapi.NN_xor:
parse instruction non-zeroing XOR instruction
ignore expected non-zeroing XORs, e.g. security cookies
"""
insn: idaapi.insn_t = ih.inner
if insn.itype not in (idaapi.NN_xor, idaapi.NN_xorpd, idaapi.NN_xorps, idaapi.NN_pxor):
return
if capa.features.extractors.ida.helpers.is_operand_equal(insn.Op1, insn.Op2):
return
if is_nzxor_stack_cookie(f, bb, insn):
if is_nzxor_stack_cookie(fh.inner, bbh.inner, insn):
return
yield Characteristic("nzxor"), insn.ea
yield Characteristic("nzxor"), ih.address
def extract_insn_mnemonic_features(f, bb, insn):
"""parse instruction mnemonic features
def extract_insn_mnemonic_features(
fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle
) -> Iterator[Tuple[Feature, Address]]:
"""parse instruction mnemonic features"""
yield Mnemonic(idc.print_insn_mnem(ih.inner.ea)), ih.address
args:
f (IDA func_t)
bb (IDA BasicBlock)
insn (IDA insn_t)
def extract_insn_obfs_call_plus_5_characteristic_features(
fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle
) -> Iterator[Tuple[Feature, Address]]:
"""
yield Mnemonic(insn.get_canon_mnem()), insn.ea
parse call $+5 instruction from the given instruction.
"""
insn: idaapi.insn_t = ih.inner
if not idaapi.is_call_insn(insn):
return
if insn.ea + 5 == idc.get_operand_value(insn.ea, 0):
yield Characteristic("call $+5"), ih.address
def extract_insn_peb_access_characteristic_features(f, bb, insn):
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
@@ -311,6 +393,8 @@ def extract_insn_peb_access_characteristic_features(f, bb, insn):
TODO:
IDA should be able to do this..
"""
insn: idaapi.insn_t = ih.inner
if insn.itype not in (idaapi.NN_push, idaapi.NN_mov):
return
@@ -322,15 +406,19 @@ def extract_insn_peb_access_characteristic_features(f, bb, insn):
if " fs:30h" in disasm or " gs:60h" in disasm:
# TODO: replace above with proper IDA
yield Characteristic("peb access"), insn.ea
yield Characteristic("peb access"), ih.address
def extract_insn_segment_access_features(f, bb, insn):
def extract_insn_segment_access_features(
fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle
) -> Iterator[Tuple[Feature, Address]]:
"""parse instruction fs or gs access
TODO:
IDA should be able to do this...
"""
insn: idaapi.insn_t = ih.inner
if all(map(lambda op: op.type != idaapi.o_mem, insn.ops)):
# try to optimize for only memory references
return
@@ -339,23 +427,21 @@ def extract_insn_segment_access_features(f, bb, insn):
if " fs:" in disasm:
# TODO: replace above with proper IDA
yield Characteristic("fs access"), insn.ea
yield Characteristic("fs access"), ih.address
if " gs:" in disasm:
# TODO: replace above with proper IDA
yield Characteristic("gs access"), insn.ea
yield Characteristic("gs access"), ih.address
def extract_insn_cross_section_cflow(f, bb, insn):
"""inspect the instruction for a CALL or JMP that crosses section boundaries
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"""
insn: idaapi.insn_t = ih.inner
args:
f (IDA func_t)
bb (IDA BasicBlock)
insn (IDA insn_t)
"""
for ref in idautils.CodeRefsFrom(insn.ea, False):
if ref in get_imports(f.ctx).keys():
if ref in get_imports(fh.ctx).keys():
# ignore API calls
continue
if not idaapi.getseg(ref):
@@ -363,50 +449,40 @@ def extract_insn_cross_section_cflow(f, bb, insn):
continue
if idaapi.getseg(ref) == idaapi.getseg(insn.ea):
continue
yield Characteristic("cross section flow"), insn.ea
yield Characteristic("cross section flow"), ih.address
def extract_function_calls_from(f, bb, insn):
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
args:
f (IDA func_t)
bb (IDA BasicBlock)
insn (IDA insn_t)
"""
insn: idaapi.insn_t = ih.inner
if idaapi.is_call_insn(insn):
for ref in idautils.CodeRefsFrom(insn.ea, False):
yield Characteristic("calls from"), ref
yield Characteristic("calls from"), AbsoluteVirtualAddress(ref)
def extract_function_indirect_call_characteristic_features(f, bb, insn):
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
args:
f (IDA func_t)
bb (IDA BasicBlock)
insn (IDA insn_t)
"""
insn: idaapi.insn_t = ih.inner
if idaapi.is_call_insn(insn) and idc.get_operand_type(insn.ea, 0) in (idc.o_reg, idc.o_phrase, idc.o_displ):
yield Characteristic("indirect call"), insn.ea
yield Characteristic("indirect call"), ih.address
def extract_features(f, bb, insn):
"""extract instruction features
args:
f (IDA func_t)
bb (IDA BasicBlock)
insn (IDA insn_t)
"""
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, bb, insn):
for feature, ea in inst_handler(f, bbh, insn):
yield feature, ea
@@ -418,6 +494,7 @@ INSTRUCTION_HANDLERS = (
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,

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
@@ -6,7 +6,7 @@
# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and limitations under the License.
from networkx import nx
import networkx
from networkx.algorithms.components import strongly_connected_components
@@ -20,6 +20,6 @@ def has_loop(edges, threshold=2):
returns:
bool
"""
g = nx.DiGraph()
g = networkx.DiGraph()
g.add_edges_from(edges)
return any(len(comp) >= threshold for comp in strongly_connected_components(g))

View File

@@ -0,0 +1,72 @@
from typing import Dict, List, Tuple
from dataclasses import dataclass
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
@dataclass
class InstructionFeatures:
features: List[Tuple[Address, Feature]]
@dataclass
class BasicBlockFeatures:
features: List[Tuple[Address, Feature]]
instructions: Dict[Address, InstructionFeatures]
@dataclass
class FunctionFeatures:
features: List[Tuple[Address, Feature]]
basic_blocks: Dict[Address, BasicBlockFeatures]
@dataclass
class NullFeatureExtractor(FeatureExtractor):
"""
An extractor that extracts some user-provided features.
This is useful for testing, as we can provide expected values and see if matching works.
"""
base_address: Address
global_features: List[Feature]
file_features: List[Tuple[Address, Feature]]
functions: Dict[Address, FunctionFeatures]
def get_base_address(self):
return self.base_address
def extract_global_features(self):
for feature in self.global_features:
yield feature, NO_ADDRESS
def extract_file_features(self):
for address, feature in self.file_features:
yield feature, address
def get_functions(self):
for address in sorted(self.functions.keys()):
yield FunctionHandle(address, None)
def extract_function_features(self, f):
for address, feature in self.functions[f.address].features:
yield feature, address
def get_basic_blocks(self, f):
for address in sorted(self.functions[f.address].basic_blocks.keys()):
yield BBHandle(address, None)
def extract_basic_block_features(self, f, bb):
for address, feature in self.functions[f.address].basic_blocks[bb.address].features:
yield feature, address
def get_instructions(self, f, bb):
for address in sorted(self.functions[f.address].basic_blocks[bb.address].instructions.keys()):
yield InsnHandle(address, None)
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

View File

@@ -0,0 +1,218 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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 pefile
import capa.features.common
import capa.features.extractors
import capa.features.extractors.common
import capa.features.extractors.helpers
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
logger = logging.getLogger(__name__)
def extract_file_embedded_pe(buf, **kwargs):
for offset, _ in capa.features.extractors.helpers.carve_pe(buf, 1):
yield Characteristic("embedded pe"), FileOffsetAddress(offset)
def extract_file_export_names(pe, **kwargs):
base_address = pe.OPTIONAL_HEADER.ImageBase
if hasattr(pe, "DIRECTORY_ENTRY_EXPORT"):
for export in pe.DIRECTORY_ENTRY_EXPORT.symbols:
if not export.name:
continue
try:
name = export.name.partition(b"\x00")[0].decode("ascii")
except UnicodeDecodeError:
continue
va = base_address + export.address
yield Export(name), AbsoluteVirtualAddress(va)
def extract_file_import_names(pe, **kwargs):
"""
extract imported function names
1. imports by ordinal:
- modulename.#ordinal
2. imports by name, results in two features to support importname-only matching:
- modulename.importname
- importname
"""
if hasattr(pe, "DIRECTORY_ENTRY_IMPORT"):
for dll in pe.DIRECTORY_ENTRY_IMPORT:
try:
modname = dll.dll.partition(b"\x00")[0].decode("ascii")
except UnicodeDecodeError:
continue
# strip extension
modname = modname.rpartition(".")[0].lower()
for imp in dll.imports:
if imp.import_by_ordinal:
impname = "#%s" % 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):
yield Import(name), AbsoluteVirtualAddress(imp.address)
def extract_file_section_names(pe, **kwargs):
base_address = pe.OPTIONAL_HEADER.ImageBase
for section in pe.sections:
try:
name = section.Name.partition(b"\x00")[0].decode("ascii")
except UnicodeDecodeError:
continue
yield Section(name), AbsoluteVirtualAddress(base_address + section.VirtualAddress)
def extract_file_strings(buf, **kwargs):
yield from capa.features.extractors.common.extract_file_strings(buf)
def extract_file_function_names(**kwargs):
"""
extract the names of statically-linked library functions.
"""
if False:
# using a `yield` here to force this to be a generator, not function.
yield NotImplementedError("pefile doesn't have library matching")
return
def extract_file_os(**kwargs):
# assuming PE -> Windows
# though i suppose they're also used by UEFI
yield OS(OS_WINDOWS), NO_ADDRESS
def extract_file_format(**kwargs):
yield Format(FORMAT_PE), NO_ADDRESS
def extract_file_arch(pe, **kwargs):
if pe.FILE_HEADER.Machine == pefile.MACHINE_TYPE["IMAGE_FILE_MACHINE_I386"]:
yield Arch(ARCH_I386), NO_ADDRESS
elif pe.FILE_HEADER.Machine == pefile.MACHINE_TYPE["IMAGE_FILE_MACHINE_AMD64"]:
yield Arch(ARCH_AMD64), NO_ADDRESS
else:
logger.warning("unsupported architecture: %s", pefile.MACHINE_TYPE[pe.FILE_HEADER.Machine])
def extract_file_features(pe, buf):
"""
extract file features from given workspace
args:
pe (pefile.PE): the parsed PE
buf: the raw sample bytes
yields:
Tuple[Feature, VA]: a feature and its location.
"""
for file_handler in FILE_HANDLERS:
# file_handler: type: (pe, bytes) -> Iterable[Tuple[Feature, Address]]
for feature, va in file_handler(pe=pe, buf=buf): # type: ignore
yield feature, va
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 extract_global_features(pe, buf):
"""
extract global features from given workspace
args:
pe (pefile.PE): the parsed PE
buf: the raw sample bytes
yields:
Tuple[Feature, VA]: a feature and its location.
"""
for handler in GLOBAL_HANDLERS:
# file_handler: type: (pe, bytes) -> Iterable[Tuple[Feature, Address]]
for feature, va in handler(pe=pe, buf=buf): # type: ignore
yield feature, va
GLOBAL_HANDLERS = (
extract_file_os,
extract_file_arch,
)
class PefileFeatureExtractor(FeatureExtractor):
def __init__(self, path: str):
super().__init__()
self.path = path
self.pe = pefile.PE(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()
yield from extract_global_features(self.pe, buf)
def extract_file_features(self):
with open(self.path, "rb") as f:
buf = f.read()
yield from extract_file_features(self.pe, buf)
def get_functions(self):
raise NotImplementedError("PefileFeatureExtract can only be used to extract file features")
def extract_function_features(self, f):
raise NotImplementedError("PefileFeatureExtract can only be used to extract file features")
def get_basic_blocks(self, f):
raise NotImplementedError("PefileFeatureExtract can only be used to extract file features")
def extract_basic_block_features(self, f, bb):
raise NotImplementedError("PefileFeatureExtract can only be used to extract file features")
def get_instructions(self, f, bb):
raise NotImplementedError("PefileFeatureExtract can only be used to extract file features")
def extract_insn_features(self, f, bb, insn):
raise NotImplementedError("PefileFeatureExtract can only be used to extract file features")
def is_library_function(self, va):
raise NotImplementedError("PefileFeatureExtract can only be used to extract file features")
def get_function_name(self, va):
raise NotImplementedError("PefileFeatureExtract can only be used to extract file features")

View File

@@ -1,6 +1,6 @@
# strings code from FLOSS, https://github.com/fireeye/flare-floss
# strings code from FLOSS, https://github.com/mandiant/flare-floss
#
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt

View File

@@ -1,85 +0,0 @@
# Copyright (C) 2020 FireEye, 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 types
import file
import insn
import function
import viv_utils
import basicblock
import capa.features.extractors
import capa.features.extractors.viv.file
import capa.features.extractors.viv.insn
import capa.features.extractors.viv.function
import capa.features.extractors.viv.basicblock
from capa.features.extractors import FeatureExtractor
__all__ = ["file", "function", "basicblock", "insn"]
def get_va(self):
try:
# vivisect type
return self.va
except AttributeError:
pass
raise TypeError()
def add_va_int_cast(o):
"""
dynamically add a cast-to-int (`__int__`) method to the given object
that returns the value of the `.va` property.
this bit of skullduggery lets use cast viv-utils objects as ints.
the correct way of doing this is to update viv-utils (or subclass the objects here).
"""
setattr(o, "__int__", types.MethodType(get_va, o, type(o)))
return o
class VivisectFeatureExtractor(FeatureExtractor):
def __init__(self, vw, path):
super(VivisectFeatureExtractor, self).__init__()
self.vw = vw
self.path = path
def get_base_address(self):
# assume there is only one file loaded into the vw
return list(self.vw.filemeta.values())[0]["imagebase"]
def extract_file_features(self):
for feature, va in capa.features.extractors.viv.file.extract_features(self.vw, self.path):
yield feature, va
def get_functions(self):
for va in sorted(self.vw.getFunctions()):
yield add_va_int_cast(viv_utils.Function(self.vw, va))
def extract_function_features(self, f):
for feature, va in capa.features.extractors.viv.function.extract_features(f):
yield feature, va
def get_basic_blocks(self, f):
for bb in f.basic_blocks:
yield add_va_int_cast(bb)
def extract_basic_block_features(self, f, bb):
for feature, va in capa.features.extractors.viv.basicblock.extract_features(f, bb):
yield feature, va
def get_instructions(self, f, bb):
for insn in bb.instructions:
yield add_va_int_cast(insn)
def extract_insn_features(self, f, bb, insn):
for feature, va in capa.features.extractors.viv.insn.extract_features(f, bb, insn):
yield feature, va

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
@@ -8,27 +8,30 @@
import string
import struct
from typing import Tuple, Iterator
import envi
import vivisect.const
import envi.archs.i386.disasm
from capa.features import Characteristic
from capa.features.common import Feature, Characteristic
from capa.features.address import Address, AbsoluteVirtualAddress
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 interface_extract_basic_block_XXX(f, bb):
def interface_extract_basic_block_XXX(f: FunctionHandle, bb: BBHandle) -> Iterator[Tuple[Feature, Address]]:
"""
parse features from the given basic block.
args:
f (viv_utils.Function): the function to process.
bb (viv_utils.BasicBlock): the basic block to process.
f: the function to process.
bb: the basic block to process.
yields:
(Feature, int): the feature and the address at which its found.
(Feature, Address): the feature and the address at which its found.
"""
yield NotImplementedError("feature"), NotImplementedError("virtual address")
raise NotImplementedError
def _bb_has_tight_loop(f, bb):
@@ -37,17 +40,17 @@ def _bb_has_tight_loop(f, bb):
"""
if len(bb.instructions) > 0:
for bva, bflags in bb.instructions[-1].getBranches():
if bflags & vivisect.envi.BR_COND:
if bflags & envi.BR_COND:
if bva == bb.va:
return True
return False
def extract_bb_tight_loop(f, bb):
""" check basic block for tight loop indicators """
if _bb_has_tight_loop(f, bb):
yield Characteristic("tight loop"), bb.va
def extract_bb_tight_loop(f: FunctionHandle, bb: BBHandle) -> Iterator[Tuple[Feature, Address]]:
"""check basic block for tight loop indicators"""
if _bb_has_tight_loop(f, bb.inner):
yield Characteristic("tight loop"), bb.address
def _bb_has_stackstring(f, bb):
@@ -67,13 +70,13 @@ def _bb_has_stackstring(f, bb):
return False
def extract_stackstring(f, bb):
""" check basic block for stackstring indicators """
if _bb_has_stackstring(f, bb):
yield Characteristic("stack string"), bb.va
def extract_stackstring(f: FunctionHandle, bb: BBHandle) -> Iterator[Tuple[Feature, Address]]:
"""check basic block for stackstring indicators"""
if _bb_has_stackstring(f, bb.inner):
yield Characteristic("stack string"), bb.address
def is_mov_imm_to_stack(instr):
def is_mov_imm_to_stack(instr: envi.archs.i386.disasm.i386Opcode) -> bool:
"""
Return if instruction moves immediate onto stack
"""
@@ -105,7 +108,7 @@ def is_mov_imm_to_stack(instr):
return True
def get_printable_len(oper):
def get_printable_len(oper: envi.archs.i386.disasm.i386ImmOper) -> int:
"""
Return string length if all operand bytes are ascii or utf16-le printable
"""
@@ -117,23 +120,33 @@ def get_printable_len(oper):
chars = struct.pack("<I", oper.imm)
elif oper.tsize == 8:
chars = struct.pack("<Q", oper.imm)
else:
raise ValueError("unexpected oper.tsize: %d" % (oper.tsize))
if is_printable_ascii(chars):
return oper.tsize
if is_printable_utf16le(chars):
elif is_printable_utf16le(chars):
return oper.tsize / 2
return 0
else:
return 0
def is_printable_ascii(chars):
return all(ord(c) < 127 and c in string.printable for c in chars)
def is_printable_ascii(chars: bytes) -> bool:
try:
chars_str = chars.decode("ascii")
except UnicodeDecodeError:
return False
else:
return all(c in string.printable for c in chars_str)
def is_printable_utf16le(chars):
if all(c == "\x00" for c in chars[1::2]):
def is_printable_utf16le(chars: bytes) -> bool:
if all(c == b"\x00" for c in chars[1::2]):
return is_printable_ascii(chars[::2])
return False
def extract_features(f, bb):
def extract_features(f: FunctionHandle, bb: BBHandle) -> Iterator[Tuple[Feature, Address]]:
"""
extract features from the given basic block.
@@ -142,12 +155,12 @@ def extract_features(f, bb):
bb (viv_utils.BasicBlock): the basic block to process.
yields:
Feature, set[VA]: the features and their location found in this basic block.
Tuple[Feature, int]: the features and their location found in this basic block.
"""
yield BasicBlock(), bb.va
yield BasicBlock(), AbsoluteVirtualAddress(bb.inner.va)
for bb_handler in BASIC_BLOCK_HANDLERS:
for feature, va in bb_handler(f, bb):
yield feature, va
for feature, addr in bb_handler(f, bb):
yield feature, addr
BASIC_BLOCK_HANDLERS = (

View File

@@ -0,0 +1,80 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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
import viv_utils
import viv_utils.flirt
import capa.features.extractors.common
import capa.features.extractors.viv.file
import capa.features.extractors.viv.insn
import capa.features.extractors.viv.global_
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
logger = logging.getLogger(__name__)
class VivisectFeatureExtractor(FeatureExtractor):
def __init__(self, vw, path):
super().__init__()
self.vw = vw
self.path = path
with open(self.path, "rb") as f:
self.buf = f.read()
# 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.viv.global_.extract_arch(self.vw))
def get_base_address(self):
# assume there is only one file loaded into the vw
return AbsoluteVirtualAddress(list(self.vw.filemeta.values())[0]["imagebase"])
def extract_global_features(self):
yield from self.global_features
def extract_file_features(self):
yield from capa.features.extractors.viv.file.extract_features(self.vw, self.buf)
def get_functions(self) -> Iterator[FunctionHandle]:
for va in sorted(self.vw.getFunctions()):
yield FunctionHandle(address=AbsoluteVirtualAddress(va), inner=viv_utils.Function(self.vw, va))
def extract_function_features(self, fh: FunctionHandle) -> Iterator[Tuple[Feature, Address]]:
yield from capa.features.extractors.viv.function.extract_features(fh)
def get_basic_blocks(self, fh: FunctionHandle) -> Iterator[BBHandle]:
f: viv_utils.Function = fh.inner
for bb in f.basic_blocks:
yield BBHandle(address=AbsoluteVirtualAddress(bb.va), inner=bb)
def extract_basic_block_features(self, fh: FunctionHandle, bbh) -> Iterator[Tuple[Feature, Address]]:
yield from capa.features.extractors.viv.basicblock.extract_features(fh, bbh)
def get_instructions(self, fh: FunctionHandle, bbh: BBHandle) -> Iterator[InsnHandle]:
bb: viv_utils.BasicBlock = bbh.inner
for insn in bb.instructions:
yield InsnHandle(address=AbsoluteVirtualAddress(insn.va), inner=insn)
def extract_insn_features(
self, fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle
) -> Iterator[Tuple[Feature, Address]]:
yield from capa.features.extractors.viv.insn.extract_features(fh, bbh, ih)
def is_library_function(self, addr):
return viv_utils.flirt.is_library_function(self.vw, addr)
def get_function_name(self, addr):
return viv_utils.get_function_name(self.vw, addr)

View File

@@ -1,33 +1,36 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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 PE.carve as pe_carve # vivisect PE
import viv_utils
import viv_utils.flirt
import capa.features.insn
import capa.features.extractors.common
import capa.features.extractors.helpers
import capa.features.extractors.strings
from capa.features import String, Characteristic
from capa.features.file import Export, Import, Section
from capa.features.file import Export, Import, Section, FunctionName
from capa.features.common import String, Feature, Characteristic
from capa.features.address import Address, FileOffsetAddress, AbsoluteVirtualAddress
def extract_file_embedded_pe(vw, file_path):
with open(file_path, "rb") as f:
fbytes = f.read()
for offset, i in pe_carve.carve(fbytes, 1):
yield Characteristic("embedded pe"), offset
def extract_file_embedded_pe(buf, **kwargs) -> Iterator[Tuple[Feature, Address]]:
for offset, _ in pe_carve.carve(buf, 1):
yield Characteristic("embedded pe"), FileOffsetAddress(offset)
def extract_file_export_names(vw, file_path):
for va, etype, name, _ in vw.getExports():
yield Export(name), va
def extract_file_export_names(vw, **kwargs) -> Iterator[Tuple[Feature, Address]]:
for va, _, name, _ in vw.getExports():
yield Export(name), AbsoluteVirtualAddress(va)
def extract_file_import_names(vw, file_path):
def extract_file_import_names(vw, **kwargs) -> Iterator[Tuple[Feature, Address]]:
"""
extract imported function names
1. imports by ordinal:
@@ -38,16 +41,17 @@ def extract_file_import_names(vw, file_path):
"""
for va, _, _, tinfo in vw.getImports():
# vivisect source: tinfo = "%s.%s" % (libname, impname)
modname, impname = tinfo.split(".")
modname, impname = tinfo.split(".", 1)
if is_viv_ord_impname(impname):
# replace ord prefix with #
impname = "#%s" % impname[len("ord") :]
addr = AbsoluteVirtualAddress(va)
for name in capa.features.extractors.helpers.generate_symbols(modname, impname):
yield Import(name), va
yield Import(name), addr
def is_viv_ord_impname(impname):
def is_viv_ord_impname(impname: str) -> bool:
"""
return if import name matches vivisect's ordinal naming scheme `'ord%d' % ord`
"""
@@ -61,40 +65,51 @@ def is_viv_ord_impname(impname):
return True
def extract_file_section_names(vw, file_path):
def extract_file_section_names(vw, **kwargs) -> Iterator[Tuple[Feature, Address]]:
for va, _, segname, _ in vw.getSegments():
yield Section(segname), va
yield Section(segname), AbsoluteVirtualAddress(va)
def extract_file_strings(vw, file_path):
def extract_file_strings(buf, **kwargs) -> Iterator[Tuple[Feature, Address]]:
yield from capa.features.extractors.common.extract_file_strings(buf)
def extract_file_function_names(vw, **kwargs) -> Iterator[Tuple[Feature, Address]]:
"""
extract ASCII and UTF-16 LE strings from file
extract the names of statically-linked library functions.
"""
with open(file_path, "rb") as f:
b = f.read()
for s in capa.features.extractors.strings.extract_ascii_strings(b):
yield String(s.s), s.offset
for s in capa.features.extractors.strings.extract_unicode_strings(b):
yield String(s.s), s.offset
for va in sorted(vw.getFunctions()):
addr = AbsoluteVirtualAddress(va)
if viv_utils.flirt.is_library_function(vw, va):
name = viv_utils.get_function_name(vw, va)
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_features(vw, file_path):
def extract_file_format(buf, **kwargs) -> Iterator[Tuple[Feature, Address]]:
yield from capa.features.extractors.common.extract_format(buf)
def extract_features(vw, buf: bytes) -> Iterator[Tuple[Feature, Address]]:
"""
extract file features from given workspace
args:
vw (vivisect.VivWorkspace): the vivisect workspace
file_path: path to the input file
buf: the raw input file bytes
yields:
Tuple[Feature, VA]: a feature and its location.
Tuple[Feature, Address]: a feature and its location.
"""
for file_handler in FILE_HANDLERS:
for feature, va in file_handler(vw, file_path):
yield feature, va
for feature, addr in file_handler(vw=vw, buf=buf): # type: ignore
yield feature, addr
FILE_HANDLERS = (
@@ -103,4 +118,6 @@ FILE_HANDLERS = (
extract_file_import_names,
extract_file_section_names,
extract_file_strings,
extract_file_function_names,
extract_file_format,
)

View File

@@ -1,39 +1,47 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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 envi
import viv_utils
import vivisect.const
from capa.features import Characteristic
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 interface_extract_function_XXX(f):
def interface_extract_function_XXX(fh: FunctionHandle) -> Iterator[Tuple[Feature, Address]]:
"""
parse features from the given function.
args:
f (viv_utils.Function): the function to process.
f: the function to process.
yields:
(Feature, int): the feature and the address at which its found.
(Feature, Address): the feature and the address at which its found.
"""
yield NotImplementedError("feature"), NotImplementedError("virtual address")
raise NotImplementedError
def extract_function_calls_to(f):
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):
yield Characteristic("calls to"), src
yield Characteristic("calls to"), AbsoluteVirtualAddress(src)
def extract_function_loop(f):
def extract_function_loop(fhandle: FunctionHandle) -> Iterator[Tuple[Feature, Address]]:
"""
parse if a function has a loop
"""
f: viv_utils.Function = fhandle.inner
edges = []
for bb in f.basic_blocks:
@@ -41,30 +49,30 @@ def extract_function_loop(f):
for bva, bflags in bb.instructions[-1].getBranches():
# vivisect does not set branch flags for non-conditional jmp so add explicit check
if (
bflags & vivisect.envi.BR_COND
or bflags & vivisect.envi.BR_FALL
or bflags & vivisect.envi.BR_TABLE
bflags & envi.BR_COND
or bflags & envi.BR_FALL
or bflags & envi.BR_TABLE
or bb.instructions[-1].mnem == "jmp"
):
edges.append((bb.va, bva))
if edges and loops.has_loop(edges):
yield Characteristic("loop"), f.va
yield Characteristic("loop"), fhandle.address
def extract_features(f):
def extract_features(fh: FunctionHandle) -> Iterator[Tuple[Feature, Address]]:
"""
extract features from the given function.
args:
f (viv_utils.Function): the function from which to extract features
fh: the function handle from which to extract features
yields:
Feature, set[VA]: the features and their location found in this function.
Tuple[Feature, int]: the features and their location found in this function.
"""
for func_handler in FUNCTION_HANDLERS:
for feature, va in func_handler(f):
yield feature, va
for feature, addr in func_handler(fh):
yield feature, addr
FUNCTION_HANDLERS = (extract_function_calls_to, extract_function_loop)

View File

@@ -0,0 +1,26 @@
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
logger = logging.getLogger(__name__)
def extract_arch(vw) -> Iterator[Tuple[Feature, Address]]:
if isinstance(vw.arch, envi.archs.amd64.Amd64Module):
yield Arch(ARCH_AMD64), NO_ADDRESS
elif isinstance(vw.arch, envi.archs.i386.i386Module):
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", vw.arch.__class__.__name__)
return

View File

@@ -0,0 +1,23 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# Unless required by applicable law or agreed to in writing, software distributed under the License
# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and limitations under the License.
from typing import Optional
from vivisect import VivWorkspace
from vivisect.const import XR_TO, REF_CODE
def get_coderef_from(vw: VivWorkspace, va: int) -> Optional[int]:
"""
return first code `tova` whose origin is the specified va
return None if no code reference is found
"""
xrefs = vw.getXrefsFrom(va, REF_CODE)
if len(xrefs) > 0:
return xrefs[0][XR_TO]
else:
return None

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
@@ -7,11 +7,13 @@
# 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
import envi
import vivisect.const
import envi.archs.i386.disasm
import envi.archs.amd64.disasm
from vivisect import VivWorkspace
# pull out consts for lookup performance
i386RegOper = envi.archs.i386.disasm.i386RegOper
@@ -26,7 +28,7 @@ FAR_BRANCH_MASK = envi.BR_PROC | envi.BR_DEREF | envi.BR_ARCH
DESTRUCTIVE_MNEMONICS = ("mov", "lea", "pop", "xor")
def get_previous_instructions(vw, va):
def get_previous_instructions(vw: VivWorkspace, va: int) -> List[int]:
"""
collect the instructions that flow to the given address, local to the current function.
@@ -40,22 +42,24 @@ def get_previous_instructions(vw, va):
ret = []
# find the immediate prior instruction.
# ensure that it fallsthrough to this one.
# ensure that it falls through to this one.
loc = vw.getPrevLocation(va, adjacent=True)
if loc is not None:
# from vivisect.const:
# location: (L_VA, L_SIZE, L_LTYPE, L_TINFO)
(pva, _, ptype, pinfo) = vw.getPrevLocation(va, adjacent=True)
ploc = vw.getPrevLocation(va, adjacent=True)
if ploc is not None:
# from vivisect.const:
# location: (L_VA, L_SIZE, L_LTYPE, L_TINFO)
(pva, _, ptype, pinfo) = ploc
if ptype == LOC_OP and not (pinfo & IF_NOFALL):
ret.append(pva)
if ptype == LOC_OP and not (pinfo & IF_NOFALL):
ret.append(pva)
# find any code refs, e.g. jmp, to this location.
# ignore any calls.
#
# from vivisect.const:
# xref: (XR_FROM, XR_TO, XR_RTYPE, XR_RFLAG)
for (xfrom, _, _, xflag) in vw.getXrefsTo(va, REF_CODE):
for xfrom, _, _, xflag in vw.getXrefsTo(va, REF_CODE):
if (xflag & FAR_BRANCH_MASK) != 0:
continue
ret.append(xfrom)
@@ -67,7 +71,7 @@ class NotFoundError(Exception):
pass
def find_definition(vw, va, reg):
def find_definition(vw: VivWorkspace, va: int, reg: int) -> Tuple[int, Union[int, None]]:
"""
scan backwards from the given address looking for assignments to the given register.
if a constant, return that value.
@@ -83,8 +87,8 @@ def find_definition(vw, va, reg):
raises:
NotFoundError: when the definition cannot be found.
"""
q = collections.deque()
seen = set([])
q = collections.deque() # type: Deque[int]
seen = set([]) # type: Set[int]
q.extend(get_previous_instructions(vw, va))
while q:
@@ -128,14 +132,14 @@ def find_definition(vw, va, reg):
raise NotFoundError()
def is_indirect_call(vw, va, insn=None):
def is_indirect_call(vw: VivWorkspace, va: int, insn: envi.Opcode) -> bool:
if insn is None:
insn = vw.parseOpcode(va)
return insn.mnem == "call" and isinstance(insn.opers[0], envi.archs.i386.disasm.i386RegOper)
return insn.mnem in ("call", "jmp") and isinstance(insn.opers[0], envi.archs.i386.disasm.i386RegOper)
def resolve_indirect_call(vw, va, insn=None):
def resolve_indirect_call(vw: VivWorkspace, va: int, insn: envi.Opcode) -> Tuple[int, Optional[int]]:
"""
inspect the given indirect call instruction and attempt to resolve the target address.

View File

@@ -1,18 +1,28 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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, Callable, Iterator
import envi
import envi.exc
import viv_utils
import envi.memory
import vivisect.const
import viv_utils.flirt
import envi.archs.i386.regs
import envi.archs.amd64.regs
import envi.archs.i386.disasm
import envi.archs.amd64.disasm
import capa.features.extractors.helpers
from capa.features import ARCH_X32, ARCH_X64, MAX_BYTES_FEATURE_SIZE, Bytes, String, Characteristic
from capa.features.insn import API, Number, Offset, Mnemonic
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.base_extractor import BBHandle, InsnHandle, FunctionHandle
from capa.features.extractors.viv.indirect_calls import NotFoundError, resolve_indirect_call
# security cookie checks may perform non-zeroing XORs, these are expected within a certain
@@ -20,27 +30,21 @@ from capa.features.extractors.viv.indirect_calls import NotFoundError, resolve_i
SECURITY_COOKIE_BYTES_DELTA = 0x40
def get_arch(vw):
arch = vw.getMeta("Architecture")
if arch == "i386":
return ARCH_X32
elif arch == "amd64":
return ARCH_X64
def interface_extract_instruction_XXX(f, bb, insn):
def interface_extract_instruction_XXX(
fh: FunctionHandle, bbh: BBHandle, ih: InsnHandle
) -> Iterator[Tuple[Feature, Address]]:
"""
parse features from the given instruction.
args:
f (viv_utils.Function): the function to process.
bb (viv_utils.BasicBlock): the basic block to process.
insn (vivisect...Instruction): the instruction to process.
fh: the function handle to process.
bbh: the basic block handle to process.
ih: the instruction handle to process.
yields:
(Feature, int): the feature and the address at which its found.
(Feature, Address): the feature and the address at which its found.
"""
yield NotImplementedError("feature"), NotImplementedError("virtual address")
raise NotImplementedError
def get_imports(vw):
@@ -60,16 +64,22 @@ def get_imports(vw):
return imports
def extract_insn_api_features(f, bb, insn):
"""parse API features from the given instruction."""
def extract_insn_api_features(fh: FunctionHandle, bb, ih: InsnHandle) -> Iterator[Tuple[Feature, Address]]:
"""
parse API features from the given instruction.
# example:
#
# call dword [0x00473038]
if insn.mnem != "call":
example:
call dword [0x00473038]
"""
insn: envi.Opcode = ih.inner
f: viv_utils.Function = fh.inner
if insn.mnem not in ("call", "jmp"):
return
if insn.mnem == "jmp":
if f.vw.getFunctionMeta(f.va, "Thunk"):
return
# traditional call via IAT
if isinstance(insn.opers[0], envi.archs.i386.disasm.i386ImmMemOper):
oper = insn.opers[0]
@@ -79,27 +89,50 @@ def extract_insn_api_features(f, bb, insn):
if target in imports:
dll, symbol = imports[target]
for name in capa.features.extractors.helpers.generate_symbols(dll, symbol):
yield API(name), insn.va
yield API(name), ih.address
# call via thunk on x86,
# see 9324d1a8ae37a36ae560c37448c9705a at 0x407985
#
# this is also how calls to internal functions may be decoded on x64.
# this is also how calls to internal functions may be decoded on x32 and x64.
# see Lab21-01.exe_:0x140001178
elif isinstance(insn.opers[0], envi.archs.i386.disasm.i386PcRelOper):
target = insn.opers[0].getOperValue(insn)
#
# follow chained thunks, e.g. in 82bf6347acf15e5d883715dc289d8a2b at 0x14005E0FF in
# 0x140059342 (viv) / 0x14005E0C0 (IDA)
# 14005E0FF call j_ElfClearEventLogFileW (14005AAF8)
# 14005AAF8 jmp ElfClearEventLogFileW (14005E196)
# 14005E196 jmp cs:__imp_ElfClearEventLogFileW
try:
thunk = f.vw.getFunctionMeta(target, "Thunk")
except vivisect.exc.InvalidFunction:
elif isinstance(insn.opers[0], envi.archs.i386.disasm.i386PcRelOper):
imports = get_imports(f.vw)
target = capa.features.extractors.viv.helpers.get_coderef_from(f.vw, insn.va)
if not target:
return
else:
if thunk:
dll, _, symbol = thunk.rpartition(".")
if symbol.startswith("ord"):
symbol = "#" + symbol[len("ord") :]
if viv_utils.flirt.is_library_function(f.vw, target):
name = viv_utils.get_function_name(f.vw, target)
yield API(name), ih.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 API(name[1:]), ih.address
return
for _ in range(THUNK_CHAIN_DEPTH_DELTA):
if target in imports:
dll, symbol = imports[target]
for name in capa.features.extractors.helpers.generate_symbols(dll, symbol):
yield API(name), insn.va
yield API(name), ih.address
# if jump leads to an ENDBRANCH instruction, skip it
if f.vw.getByteDef(target)[1].startswith(b"\xf3\x0f\x1e"):
target += 4
target = capa.features.extractors.viv.helpers.get_coderef_from(f.vw, target)
if not target:
return
# call via import on x64
# see Lab21-01.exe_:0x14000118C
@@ -111,7 +144,7 @@ def extract_insn_api_features(f, bb, insn):
if target in imports:
dll, symbol = imports[target]
for name in capa.features.extractors.helpers.generate_symbols(dll, symbol):
yield API(name), insn.va
yield API(name), ih.address
elif isinstance(insn.opers[0], envi.archs.i386.disasm.i386RegOper):
try:
@@ -128,38 +161,7 @@ def extract_insn_api_features(f, bb, insn):
if target in imports:
dll, symbol = imports[target]
for name in capa.features.extractors.helpers.generate_symbols(dll, symbol):
yield API(name), insn.va
def extract_insn_number_features(f, bb, insn):
"""parse number features from the given instruction."""
# example:
#
# push 3136B0h ; dwControlCode
for oper in insn.opers:
# this is for both x32 and x64
if not isinstance(oper, (envi.archs.i386.disasm.i386ImmOper, envi.archs.i386.disasm.i386ImmMemOper)):
continue
if isinstance(oper, envi.archs.i386.disasm.i386ImmOper):
v = oper.getOperValue(oper)
else:
v = oper.getOperAddr(oper)
if f.vw.probeMemory(v, 1, envi.memory.MM_READ):
# this is a valid address
# assume its not also a constant.
continue
if insn.mnem == "add" and insn.opers[0].isReg() and insn.opers[0].reg == envi.archs.i386.disasm.REG_ESP:
# skip things like:
#
# .text:00401140 call sub_407E2B
# .text:00401145 add esp, 0Ch
return
yield Number(v), insn.va
yield Number(v, arch=get_arch(f.vw)), insn.va
yield API(name), ih.address
def derefs(vw, p):
@@ -194,7 +196,7 @@ def derefs(vw, p):
p = next
def read_memory(vw, va, size):
def read_memory(vw, va: int, size: int) -> bytes:
# as documented in #176, vivisect will not readMemory() when the section is not marked readable.
#
# but here, we don't care about permissions.
@@ -207,10 +209,10 @@ def read_memory(vw, va, size):
mva, msize, mperms, mfname = mmap
offset = va - mva
return mbytes[offset : offset + size]
raise envi.SegmentationViolation(va)
raise envi.exc.SegmentationViolation(va)
def read_bytes(vw, va):
def read_bytes(vw, va: int) -> bytes:
"""
read up to MAX_BYTES_FEATURE_SIZE from the given address.
@@ -219,7 +221,7 @@ def read_bytes(vw, va):
"""
segm = vw.getSegment(va)
if not segm:
raise envi.SegmentationViolation()
raise envi.exc.SegmentationViolation(va)
segm_end = segm[0] + segm[1]
try:
@@ -228,20 +230,23 @@ def read_bytes(vw, va):
return read_memory(vw, va, segm_end - va)
else:
return read_memory(vw, va, MAX_BYTES_FEATURE_SIZE)
except envi.SegmentationViolation:
except envi.exc.SegmentationViolation:
raise
def extract_insn_bytes_features(f, bb, insn):
def extract_insn_bytes_features(fh: FunctionHandle, bb, ih: InsnHandle) -> Iterator[Tuple[Feature, Address]]:
"""
parse byte sequence features from the given instruction.
example:
# push offset iid_004118d4_IShellLinkA ; riid
"""
for oper in insn.opers:
if insn.mnem == "call":
continue
insn: envi.Opcode = ih.inner
f: viv_utils.Function = fh.inner
if insn.mnem == "call":
return
for oper in insn.opers:
if isinstance(oper, envi.archs.i386.disasm.i386ImmOper):
v = oper.getOperValue(oper)
elif isinstance(oper, envi.archs.i386.disasm.i386RegMemOper):
@@ -260,27 +265,36 @@ def extract_insn_bytes_features(f, bb, insn):
for v in derefs(f.vw, v):
try:
buf = read_bytes(f.vw, v)
except envi.SegmentationViolation:
except envi.exc.SegmentationViolation:
continue
if capa.features.extractors.helpers.all_zeros(buf):
continue
yield Bytes(buf), insn.va
if f.vw.isProbablyString(v):
# don't extract byte features for obvious strings
continue
yield Bytes(buf), ih.address
def read_string(vw, offset):
def read_string(vw, offset: int) -> str:
try:
alen = vw.detectString(offset)
except envi.SegmentationViolation:
except envi.exc.SegmentationViolation:
pass
else:
if alen > 0:
return read_memory(vw, offset, alen).decode("utf-8")
buf = read_memory(vw, offset, alen)
if b"\x00" in buf:
# account for bug #1271.
# remove when vivisect is fixed.
buf = buf.partition(b"\x00")[0]
return buf.decode("utf-8")
try:
ulen = vw.detectUnicode(offset)
except envi.SegmentationViolation:
except envi.exc.SegmentationViolation:
pass
except IndexError:
# potential vivisect bug detecting Unicode at segment end
@@ -291,89 +305,29 @@ def read_string(vw, offset):
# vivisect seems to mis-detect the end unicode strings
# off by one, too short
ulen += 1
return read_memory(vw, offset, ulen).decode("utf-16")
else:
# vivisect seems to mis-detect the end unicode strings
# off by two, too short
ulen += 2
# partition to account for bug #1271.
# remove when vivisect is fixed.
return read_memory(vw, offset, ulen).decode("utf-16").partition("\x00")[0]
raise ValueError("not a string", offset)
def extract_insn_string_features(f, bb, insn):
"""parse string features from the given instruction."""
# example:
#
# push offset aAcr ; "ACR > "
for oper in insn.opers:
if isinstance(oper, envi.archs.i386.disasm.i386ImmOper):
v = oper.getOperValue(oper)
elif isinstance(oper, envi.archs.i386.disasm.i386SibOper):
# like 0x401000 in `mov eax, 0x401000[2 * ebx]`
v = oper.imm
elif isinstance(oper, envi.archs.amd64.disasm.Amd64RipRelOper):
v = oper.getOperAddr(insn)
else:
continue
for v in derefs(f.vw, v):
try:
s = read_string(f.vw, v)
except ValueError:
continue
else:
yield String(s.rstrip("\x00")), insn.va
def extract_insn_offset_features(f, bb, insn):
"""parse structure offset features from the given instruction."""
# example:
#
# .text:0040112F cmp [esi+4], ebx
for oper in insn.opers:
# this is for both x32 and x64
# like [esi + 4]
# reg ^
# disp
if isinstance(oper, envi.archs.i386.disasm.i386RegMemOper):
if oper.reg == envi.archs.i386.disasm.REG_ESP:
continue
if oper.reg == envi.archs.i386.disasm.REG_EBP:
continue
# TODO: do x64 support for real.
if oper.reg == envi.archs.amd64.disasm.REG_RBP:
continue
# viv already decodes offsets as signed
v = oper.disp
yield Offset(v), insn.va
yield Offset(v, arch=get_arch(f.vw)), insn.va
# like: [esi + ecx + 16384]
# reg ^ ^
# index ^
# disp
elif isinstance(oper, envi.archs.i386.disasm.i386SibOper):
# viv already decodes offsets as signed
v = oper.disp
yield Offset(v), insn.va
yield Offset(v, arch=get_arch(f.vw)), insn.va
def is_security_cookie(f, bb, insn):
def is_security_cookie(f, bb, insn) -> bool:
"""
check if an instruction is related to security cookie checks
"""
# security cookie check should use SP or BP
oper = insn.opers[1]
if oper.isReg() and oper.reg not in [
envi.archs.i386.disasm.REG_ESP,
envi.archs.i386.disasm.REG_EBP,
envi.archs.i386.regs.REG_ESP,
envi.archs.i386.regs.REG_EBP,
# TODO: do x64 support for real.
envi.archs.amd64.disasm.REG_RBP,
envi.archs.amd64.disasm.REG_RSP,
envi.archs.amd64.regs.REG_RBP,
envi.archs.amd64.regs.REG_RSP,
]:
return False
@@ -390,12 +344,18 @@ def is_security_cookie(f, bb, insn):
return False
def extract_insn_nzxor_characteristic_features(f, bb, insn):
def extract_insn_nzxor_characteristic_features(
fh: FunctionHandle, bbhandle: BBHandle, ih: InsnHandle
) -> Iterator[Tuple[Feature, Address]]:
"""
parse non-zeroing XOR instruction from the given instruction.
ignore expected non-zeroing XORs, e.g. security cookies.
"""
if insn.mnem != "xor":
insn: envi.Opcode = ih.inner
bb: viv_utils.BasicBlock = bbhandle.inner
f: viv_utils.Function = fh.inner
if insn.mnem not in ("xor", "xorpd", "xorps", "pxor"):
return
if insn.opers[0] == insn.opers[1]:
@@ -404,19 +364,40 @@ def extract_insn_nzxor_characteristic_features(f, bb, insn):
if is_security_cookie(f, bb, insn):
return
yield Characteristic("nzxor"), insn.va
yield Characteristic("nzxor"), ih.address
def extract_insn_mnemonic_features(f, bb, insn):
def extract_insn_mnemonic_features(f, bb, ih: InsnHandle) -> Iterator[Tuple[Feature, Address]]:
"""parse mnemonic features from the given instruction."""
yield Mnemonic(insn.mnem), insn.va
yield Mnemonic(ih.inner.mnem), ih.address
def extract_insn_peb_access_characteristic_features(f, bb, insn):
def extract_insn_obfs_call_plus_5_characteristic_features(f, bb, ih: InsnHandle) -> Iterator[Tuple[Feature, Address]]:
"""
parse call $+5 instruction from the given instruction.
"""
insn: envi.Opcode = ih.inner
if insn.mnem != "call":
return
if isinstance(insn.opers[0], envi.archs.i386.disasm.i386PcRelOper):
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 insn.va + 5 == insn.opers[0].getOperAddr(insn):
yield Characteristic("call $+5"), ih.address
def extract_insn_peb_access_characteristic_features(f, bb, ih: InsnHandle) -> Iterator[Tuple[Feature, Address]]:
"""
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"]:
return
@@ -435,7 +416,7 @@ def extract_insn_peb_access_characteristic_features(f, bb, insn):
if (isinstance(oper, envi.archs.i386.disasm.i386RegMemOper) and oper.disp == 0x30) or (
isinstance(oper, envi.archs.i386.disasm.i386ImmMemOper) and oper.imm == 0x30
):
yield Characteristic("peb access"), insn.va
yield Characteristic("peb access"), ih.address
elif "gs" in prefix:
for oper in insn.opers:
if (
@@ -443,23 +424,25 @@ def extract_insn_peb_access_characteristic_features(f, bb, insn):
or (isinstance(oper, envi.archs.amd64.disasm.i386SibOper) and oper.imm == 0x60)
or (isinstance(oper, envi.archs.amd64.disasm.i386ImmMemOper) and oper.imm == 0x60)
):
yield Characteristic("peb access"), insn.va
yield Characteristic("peb access"), ih.address
else:
pass
def extract_insn_segment_access_features(f, bb, insn):
""" parse the instruction for access to fs or gs """
def extract_insn_segment_access_features(f, bb, ih: InsnHandle) -> Iterator[Tuple[Feature, Address]]:
"""parse the instruction for access to fs or gs"""
insn: envi.Opcode = ih.inner
prefix = insn.getPrefixName()
if prefix == "fs":
yield Characteristic("fs access"), insn.va
yield Characteristic("fs access"), ih.address
if prefix == "gs":
yield Characteristic("gs access"), insn.va
yield Characteristic("gs access"), ih.address
def get_section(vw, va):
def get_section(vw, va: int):
for start, length, _, __ in vw.getMemoryMaps():
if start <= va < start + length:
return start
@@ -467,11 +450,18 @@ def get_section(vw, va):
raise KeyError(va)
def extract_insn_cross_section_cflow(f, bb, insn):
def extract_insn_cross_section_cflow(fh: FunctionHandle, bb, ih: InsnHandle) -> Iterator[Tuple[Feature, Address]]:
"""
inspect the instruction for a CALL or JMP that crosses section boundaries.
"""
insn: envi.Opcode = ih.inner
f: viv_utils.Function = fh.inner
for va, flags in insn.getBranches():
if va is None:
# va may be none for dynamic branches that haven't been resolved, such as `jmp eax`.
continue
if flags & envi.BR_FALL:
continue
@@ -493,7 +483,7 @@ def extract_insn_cross_section_cflow(f, bb, insn):
continue
if get_section(f.vw, insn.va) != get_section(f.vw, va):
yield Characteristic("cross section flow"), insn.va
yield Characteristic("cross section flow"), ih.address
except KeyError:
continue
@@ -501,7 +491,10 @@ def extract_insn_cross_section_cflow(f, bb, insn):
# this is a feature that's most relevant at the function scope,
# however, its most efficient to extract at the instruction scope.
def extract_function_calls_from(f, bb, insn):
def extract_function_calls_from(fh: FunctionHandle, bb, ih: InsnHandle) -> Iterator[Tuple[Feature, Address]]:
insn: envi.Opcode = ih.inner
f: viv_utils.Function = fh.inner
if insn.mnem != "call":
return
@@ -511,7 +504,8 @@ def extract_function_calls_from(f, bb, insn):
if isinstance(insn.opers[0], envi.archs.i386.disasm.i386ImmMemOper):
oper = insn.opers[0]
target = oper.getOperAddr(insn)
yield Characteristic("calls from"), target
if target >= 0:
yield Characteristic("calls from"), AbsoluteVirtualAddress(target)
# call via thunk on x86,
# see 9324d1a8ae37a36ae560c37448c9705a at 0x407985
@@ -520,43 +514,193 @@ def extract_function_calls_from(f, bb, insn):
# see Lab21-01.exe_:0x140001178
elif isinstance(insn.opers[0], envi.archs.i386.disasm.i386PcRelOper):
target = insn.opers[0].getOperValue(insn)
yield Characteristic("calls from"), target
if target >= 0:
yield Characteristic("calls from"), AbsoluteVirtualAddress(target)
# call via IAT, x64
elif isinstance(insn.opers[0], envi.archs.amd64.disasm.Amd64RipRelOper):
op = insn.opers[0]
target = op.getOperAddr(insn)
yield Characteristic("calls from"), target
if target >= 0:
yield Characteristic("calls from"), AbsoluteVirtualAddress(target)
if target and target == f.va:
# if we found a jump target and it's the function address
# mark as recursive
yield Characteristic("recursive call"), target
yield Characteristic("recursive call"), AbsoluteVirtualAddress(target)
# this is a feature that's most relevant at the function or basic block scope,
# however, its most efficient to extract at the instruction scope.
def extract_function_indirect_call_characteristic_features(f, bb, insn):
def extract_function_indirect_call_characteristic_features(f, bb, ih: InsnHandle) -> Iterator[Tuple[Feature, Address]]:
"""
extract indirect function call characteristic (e.g., call eax or call dword ptr [edx+4])
does not include calls like => call ds:dword_ABD4974
"""
insn: envi.Opcode = ih.inner
if insn.mnem != "call":
return
# Checks below work for x86 and x64
if isinstance(insn.opers[0], envi.archs.i386.disasm.i386RegOper):
# call edx
yield Characteristic("indirect call"), insn.va
yield Characteristic("indirect call"), ih.address
elif isinstance(insn.opers[0], envi.archs.i386.disasm.i386RegMemOper):
# call dword ptr [eax+50h]
yield Characteristic("indirect call"), insn.va
yield Characteristic("indirect call"), ih.address
elif isinstance(insn.opers[0], envi.archs.i386.disasm.i386SibOper):
# call qword ptr [rsp+78h]
yield Characteristic("indirect call"), insn.va
yield Characteristic("indirect call"), ih.address
def extract_features(f, bb, insn):
def extract_op_number_features(
fh: FunctionHandle, bb, ih: InsnHandle, i, oper: envi.Operand
) -> Iterator[Tuple[Feature, Address]]:
"""parse number features from the given operand.
example:
push 3136B0h ; dwControlCode
"""
insn: envi.Opcode = ih.inner
f: viv_utils.Function = fh.inner
# this is for both x32 and x64
if not isinstance(oper, (envi.archs.i386.disasm.i386ImmOper, envi.archs.i386.disasm.i386ImmMemOper)):
return
if isinstance(oper, envi.archs.i386.disasm.i386ImmOper):
v = oper.getOperValue(oper)
else:
v = oper.getOperAddr(oper)
if f.vw.probeMemory(v, 1, envi.memory.MM_READ):
# this is a valid address
# assume its not also a constant.
return
if insn.mnem == "add" and insn.opers[0].isReg() and insn.opers[0].reg == envi.archs.i386.regs.REG_ESP:
# skip things like:
#
# .text:00401140 call sub_407E2B
# .text:00401145 add esp, 0Ch
return
yield Number(v), ih.address
yield OperandNumber(i, v), ih.address
if insn.mnem == "add" and 0 < v < MAX_STRUCTURE_SIZE and isinstance(oper, envi.archs.i386.disasm.i386ImmOper):
# for pattern like:
#
# add eax, 0x10
#
# assume 0x10 is also an offset (imagine eax is a pointer).
yield Offset(v), ih.address
yield OperandOffset(i, v), ih.address
def extract_op_offset_features(
fh: FunctionHandle, bb, ih: InsnHandle, i, oper: envi.Operand
) -> Iterator[Tuple[Feature, Address]]:
"""parse structure offset features from the given operand."""
# example:
#
# .text:0040112F cmp [esi+4], ebx
insn: envi.Opcode = ih.inner
f: viv_utils.Function = fh.inner
# this is for both x32 and x64
# like [esi + 4]
# reg ^
# disp
if isinstance(oper, envi.archs.i386.disasm.i386RegMemOper):
if oper.reg == envi.archs.i386.regs.REG_ESP:
return
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
# viv already decodes offsets as signed
v = oper.disp
yield Offset(v), ih.address
yield OperandOffset(i, v), ih.address
if insn.mnem == "lea" and i == 1 and not f.vw.probeMemory(v, 1, envi.memory.MM_READ):
# for pattern like:
#
# lea eax, [ebx + 1]
#
# assume 1 is also an offset (imagine ebx is a zero register).
yield Number(v), ih.address
yield OperandNumber(i, v), ih.address
# like: [esi + ecx + 16384]
# reg ^ ^
# index ^
# disp
elif isinstance(oper, envi.archs.i386.disasm.i386SibOper):
# viv already decodes offsets as signed
v = oper.disp
yield Offset(v), ih.address
yield OperandOffset(i, v), ih.address
def extract_op_string_features(
fh: FunctionHandle, bb, ih: InsnHandle, i, oper: envi.Operand
) -> Iterator[Tuple[Feature, Address]]:
"""parse string features from the given operand."""
# example:
#
# push offset aAcr ; "ACR > "
insn: envi.Opcode = ih.inner
f: viv_utils.Function = fh.inner
if isinstance(oper, envi.archs.i386.disasm.i386ImmOper):
v = oper.getOperValue(oper)
elif isinstance(oper, envi.archs.i386.disasm.i386ImmMemOper):
# like 0x10056CB4 in `lea eax, dword [0x10056CB4]`
v = oper.imm
elif isinstance(oper, envi.archs.i386.disasm.i386SibOper):
# like 0x401000 in `mov eax, 0x401000[2 * ebx]`
v = oper.imm
elif isinstance(oper, envi.archs.amd64.disasm.Amd64RipRelOper):
v = oper.getOperAddr(insn)
else:
return
for v in derefs(f.vw, v):
try:
s = read_string(f.vw, v).rstrip("\x00")
except ValueError:
continue
else:
if len(s) >= 4:
yield String(s), ih.address
def extract_operand_features(f: FunctionHandle, bb, insn: InsnHandle) -> Iterator[Tuple[Feature, Address]]:
for i, oper in enumerate(insn.inner.opers):
for op_handler in OPERAND_HANDLERS:
for feature, addr in op_handler(f, bb, insn, i, oper):
yield feature, addr
OPERAND_HANDLERS: List[
Callable[[FunctionHandle, BBHandle, InsnHandle, int, envi.Operand], Iterator[Tuple[Feature, Address]]]
] = [
extract_op_number_features,
extract_op_offset_features,
extract_op_string_features,
]
def extract_features(f, bb, insn) -> Iterator[Tuple[Feature, Address]]:
"""
extract features from the given insn.
@@ -566,24 +710,23 @@ def extract_features(f, bb, insn):
insn (vivisect...Instruction): the instruction to process.
yields:
Feature, set[VA]: the features and their location found in this insn.
Tuple[Feature, Address]: the features and their location found in this insn.
"""
for insn_handler in INSTRUCTION_HANDLERS:
for feature, va in insn_handler(f, bb, insn):
yield feature, va
for feature, addr in insn_handler(f, bb, insn):
yield feature, addr
INSTRUCTION_HANDLERS = (
INSTRUCTION_HANDLERS: List[Callable[[FunctionHandle, BBHandle, InsnHandle], Iterator[Tuple[Feature, Address]]]] = [
extract_insn_api_features,
extract_insn_number_features,
extract_insn_string_features,
extract_insn_bytes_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,
)
extract_operand_features,
]

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
@@ -6,22 +6,33 @@
# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and limitations under the License.
from capa.features import Feature
from capa.features.common import Feature
class Export(Feature):
def __init__(self, value, description=None):
def __init__(self, value: str, description=None):
# value is export name
super(Export, self).__init__(value, description=description)
super().__init__(value, description=description)
class Import(Feature):
def __init__(self, value, description=None):
def __init__(self, value: str, description=None):
# value is import name
super(Import, self).__init__(value, description=description)
super().__init__(value, description=description)
class Section(Feature):
def __init__(self, value, description=None):
def __init__(self, value: str, description=None):
# value is section name
super(Section, self).__init__(value, description=description)
super().__init__(value, description=description)
class FunctionName(Feature):
"""recognized name for statically linked function"""
def __init__(self, name: str, description=None):
# value is function name
super().__init__(name, description=description)
# override the name property set by `capa.features.Feature`
# that would be `functionname` (note missing dash)
self.name = "function-name"

View File

@@ -1,286 +0,0 @@
"""
capa freeze file format: `| capa0000 | + zlib(utf-8(json(...)))`
json format:
{
'version': 1,
'functions': {
int(function va): {
'basic blocks': {
int(basic block va): {
'instructions': [instruction va, ...]
},
...
},
...
},
...
},
'scopes': {
'file': [
(str(name), [any(arg), ...], int(va), ()),
...
},
'function': [
(str(name), [any(arg), ...], int(va), (int(function va), )),
...
],
'basic block': [
(str(name), [any(arg), ...], int(va), (int(function va),
int(basic block va))),
...
],
'instruction': [
(str(name), [any(arg), ...], int(va), (int(function va),
int(basic block va),
int(instruction va))),
...
],
}
}
Copyright (C) 2020 FireEye, 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 zlib
import logging
import capa.features
import capa.features.file
import capa.features.insn
import capa.features.basicblock
import capa.features.extractors
from capa.helpers import hex
logger = logging.getLogger(__name__)
def serialize_feature(feature):
return feature.freeze_serialize()
KNOWN_FEATURES = {F.__name__: F for F in capa.features.Feature.__subclasses__()}
def deserialize_feature(doc):
F = KNOWN_FEATURES[doc[0]]
return F.freeze_deserialize(doc[1])
def dumps(extractor):
"""
serialize the given extractor to a string
args:
extractor: capa.features.extractor.FeatureExtractor:
returns:
str: the serialized features.
"""
ret = {
"version": 1,
"functions": {},
"scopes": {
"file": [],
"function": [],
"basic block": [],
"instruction": [],
},
}
for feature, va in extractor.extract_file_features():
ret["scopes"]["file"].append(serialize_feature(feature) + (hex(va), ()))
for f in extractor.get_functions():
ret["functions"][hex(f)] = {}
for feature, va in extractor.extract_function_features(f):
ret["scopes"]["function"].append(serialize_feature(feature) + (hex(va), (hex(f),)))
for bb in extractor.get_basic_blocks(f):
ret["functions"][hex(f)][hex(bb)] = []
for feature, va in extractor.extract_basic_block_features(f, bb):
ret["scopes"]["basic block"].append(
serialize_feature(feature)
+ (
hex(va),
(
hex(f),
hex(bb),
),
)
)
for insnva, insn in sorted(
[(insn.__int__(), insn) for insn in extractor.get_instructions(f, bb)], key=lambda p: p[0]
):
ret["functions"][hex(f)][hex(bb)].append(hex(insnva))
for feature, va in extractor.extract_insn_features(f, bb, insn):
ret["scopes"]["instruction"].append(
serialize_feature(feature)
+ (
hex(va),
(
hex(f),
hex(bb),
hex(insnva),
),
)
)
return json.dumps(ret)
def loads(s):
"""deserialize a set of features (as a NullFeatureExtractor) from a string."""
doc = json.loads(s)
if doc.get("version") != 1:
raise ValueError("unsupported freeze format version: %d" % (doc.get("version")))
features = {
"file features": [],
"functions": {},
}
for fva, function in doc.get("functions", {}).items():
fva = int(fva, 0x10)
features["functions"][fva] = {
"features": [],
"basic blocks": {},
}
for bbva, bb in function.items():
bbva = int(bbva, 0x10)
features["functions"][fva]["basic blocks"][bbva] = {
"features": [],
"instructions": {},
}
for insnva in bb:
insnva = int(insnva, 0x10)
features["functions"][fva]["basic blocks"][bbva]["instructions"][insnva] = {
"features": [],
}
# in the following blocks, each entry looks like:
#
# ('MatchedRule', ('foo', ), '0x401000', ('0x401000', ))
# ^^^^^^^^^^^^^ ^^^^^^^^^ ^^^^^^^^^^ ^^^^^^^^^^^^^^
# feature name args addr func/bb/insn
for feature in doc.get("scopes", {}).get("file", []):
va, loc = feature[2:]
va = int(va, 0x10)
feature = deserialize_feature(feature[:2])
features["file features"].append((va, feature))
for feature in doc.get("scopes", {}).get("function", []):
# fetch the pair like:
#
# ('0x401000', ('0x401000', ))
# ^^^^^^^^^^ ^^^^^^^^^^^^^^
# addr func/bb/insn
va, loc = feature[2:]
va = int(va, 0x10)
loc = [int(lo, 0x10) for lo in loc]
# decode the feature from the pair like:
#
# ('MatchedRule', ('foo', ))
# ^^^^^^^^^^^^^ ^^^^^^^^^
# feature name args
feature = deserialize_feature(feature[:2])
features["functions"][loc[0]]["features"].append((va, feature))
for feature in doc.get("scopes", {}).get("basic block", []):
va, loc = feature[2:]
va = int(va, 0x10)
loc = [int(lo, 0x10) for lo in loc]
feature = deserialize_feature(feature[:2])
features["functions"][loc[0]]["basic blocks"][loc[1]]["features"].append((va, feature))
for feature in doc.get("scopes", {}).get("instruction", []):
va, loc = feature[2:]
va = int(va, 0x10)
loc = [int(lo, 0x10) for lo in loc]
feature = deserialize_feature(feature[:2])
features["functions"][loc[0]]["basic blocks"][loc[1]]["instructions"][loc[2]]["features"].append((va, feature))
return capa.features.extractors.NullFeatureExtractor(features)
MAGIC = "capa0000".encode("ascii")
def dump(extractor):
"""serialize the given extractor to a byte array."""
return MAGIC + zlib.compress(dumps(extractor).encode("utf-8"))
def is_freeze(buf):
return buf[: len(MAGIC)] == MAGIC
def load(buf):
"""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"))
def main(argv=None):
import sys
import argparse
import capa.main
if argv is None:
argv = sys.argv[1:]
formats = [
("auto", "(default) detect file type automatically"),
("pe", "Windows PE file"),
("sc32", "32-bit shellcode"),
("sc64", "64-bit shellcode"),
]
format_help = ", ".join(["%s: %s" % (f[0], f[1]) for f in formats])
parser = argparse.ArgumentParser(description="save capa features to a file")
parser.add_argument("sample", type=str, help="Path to sample to analyze")
parser.add_argument("output", type=str, help="Path to output file")
parser.add_argument("-v", "--verbose", action="store_true", help="Enable verbose output")
parser.add_argument("-q", "--quiet", action="store_true", help="Disable all output but errors")
parser.add_argument(
"-f", "--format", choices=[f[0] for f in formats], default="auto", help="Select sample format, %s" % format_help
)
args = parser.parse_args(args=argv)
if args.quiet:
logging.basicConfig(level=logging.ERROR)
logging.getLogger().setLevel(logging.ERROR)
elif args.verbose:
logging.basicConfig(level=logging.DEBUG)
logging.getLogger().setLevel(logging.DEBUG)
else:
logging.basicConfig(level=logging.INFO)
logging.getLogger().setLevel(logging.INFO)
extractor = capa.main.get_extractor(args.sample, args.format)
with open(args.output, "wb") as f:
f.write(dump(extractor))
return 0
if __name__ == "__main__":
import sys
sys.exit(main())

View File

@@ -0,0 +1,395 @@
"""
capa freeze file format: `| capa0000 | + zlib(utf-8(json(...)))`
Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at: [package root]/LICENSE.txt
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 zlib
import logging
from enum import Enum
from typing import Any, List, Tuple
from pydantic import Field, BaseModel
import capa.helpers
import capa.version
import capa.features.file
import capa.features.insn
import capa.features.common
import capa.features.address
import capa.features.basicblock
import capa.features.extractors.base_extractor
from capa.helpers import assert_never
from capa.features.freeze.features import Feature, feature_from_capa
logger = logging.getLogger(__name__)
class HashableModel(BaseModel):
class Config:
frozen = True
class AddressType(str, Enum):
ABSOLUTE = "absolute"
RELATIVE = "relative"
FILE = "file"
DN_TOKEN = "dn token"
DN_TOKEN_OFFSET = "dn token offset"
NO_ADDRESS = "no address"
class Address(HashableModel):
type: AddressType
value: Any
@classmethod
def from_capa(cls, a: capa.features.address.Address) -> "Address":
if isinstance(a, capa.features.address.AbsoluteVirtualAddress):
return cls(type=AddressType.ABSOLUTE, value=int(a))
elif isinstance(a, capa.features.address.RelativeVirtualAddress):
return cls(type=AddressType.RELATIVE, value=int(a))
elif isinstance(a, capa.features.address.FileOffsetAddress):
return cls(type=AddressType.FILE, value=int(a))
elif isinstance(a, capa.features.address.DNTokenAddress):
return cls(type=AddressType.DN_TOKEN, value=int(a))
elif isinstance(a, capa.features.address.DNTokenOffsetAddress):
return cls(type=AddressType.DN_TOKEN_OFFSET, value=(a.token, a.offset))
elif a == capa.features.address.NO_ADDRESS or isinstance(a, capa.features.address._NoAddress):
return cls(type=AddressType.NO_ADDRESS, value=None)
elif isinstance(a, capa.features.address.Address) and not issubclass(type(a), capa.features.address.Address):
raise ValueError("don't use an Address instance directly")
elif isinstance(a, capa.features.address.Address):
raise ValueError("don't use an Address instance directly")
else:
assert_never(a)
def to_capa(self) -> capa.features.address.Address:
if self.type is AddressType.ABSOLUTE:
return capa.features.address.AbsoluteVirtualAddress(self.value)
elif self.type is AddressType.RELATIVE:
return capa.features.address.RelativeVirtualAddress(self.value)
elif self.type is AddressType.FILE:
return capa.features.address.FileOffsetAddress(self.value)
elif self.type is AddressType.DN_TOKEN:
return capa.features.address.DNTokenAddress(self.value)
elif self.type is AddressType.DN_TOKEN_OFFSET:
token, offset = self.value
return capa.features.address.DNTokenOffsetAddress(token, offset)
elif self.type is AddressType.NO_ADDRESS:
return capa.features.address.NO_ADDRESS
else:
assert_never(self.type)
def __lt__(self, other: "Address") -> bool:
if self.type != other.type:
return self.type < other.type
if self.type is AddressType.NO_ADDRESS:
return True
else:
return self.value < other.value
class GlobalFeature(HashableModel):
feature: Feature
class FileFeature(HashableModel):
address: Address
feature: Feature
class FunctionFeature(HashableModel):
"""
args:
function: the address of the function to which this feature belongs.
address: the address at which this feature is found.
function != address because, e.g., the feature may be found *within* the scope (function).
versus right at its starting address.
"""
function: Address
address: Address
feature: Feature
class BasicBlockFeature(HashableModel):
"""
args:
basic_block: the address of the basic block to which this feature belongs.
address: the address at which this feature is found.
basic_block != address because, e.g., the feature may be found *within* the scope (basic block).
versus right at its starting address.
"""
basic_block: Address = Field(alias="basic block")
address: Address
feature: Feature
class Config:
allow_population_by_field_name = True
class InstructionFeature(HashableModel):
"""
args:
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
address: Address
feature: Feature
class InstructionFeatures(BaseModel):
address: Address
features: Tuple[InstructionFeature, ...]
class BasicBlockFeatures(BaseModel):
address: Address
features: Tuple[BasicBlockFeature, ...]
instructions: Tuple[InstructionFeatures, ...]
class FunctionFeatures(BaseModel):
address: Address
features: Tuple[FunctionFeature, ...]
basic_blocks: Tuple[BasicBlockFeatures, ...] = Field(alias="basic blocks")
class Config:
allow_population_by_field_name = True
class Features(BaseModel):
global_: Tuple[GlobalFeature, ...] = Field(alias="global")
file: Tuple[FileFeature, ...]
functions: Tuple[FunctionFeatures, ...]
class Config:
allow_population_by_field_name = True
class Extractor(BaseModel):
name: str
version: str = capa.version.__version__
class Config:
allow_population_by_field_name = True
class Freeze(BaseModel):
version: int = 2
base_address: Address = Field(alias="base address")
extractor: Extractor
features: Features
class Config:
allow_population_by_field_name = True
def dumps(extractor: capa.features.extractors.base_extractor.FeatureExtractor) -> 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),
)
)
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),
)
)
function_features: List[FunctionFeatures] = []
for f in extractor.get_functions():
faddr = Address.from_capa(f.address)
ffeatures = [
FunctionFeature(
function=faddr,
address=Address.from_capa(addr),
feature=feature_from_capa(feature),
)
for feature, addr in extractor.extract_function_features(f)
]
basic_blocks = []
for bb in extractor.get_basic_blocks(f):
bbaddr = Address.from_capa(bb.address)
bbfeatures = [
BasicBlockFeature(
basic_block=bbaddr,
address=Address.from_capa(addr),
feature=feature_from_capa(feature),
)
for feature, addr in extractor.extract_basic_block_features(f, bb)
]
instructions = []
for insn in extractor.get_instructions(f, bb):
iaddr = Address.from_capa(insn.address)
ifeatures = [
InstructionFeature(
instruction=iaddr,
address=Address.from_capa(addr),
feature=feature_from_capa(feature),
)
for feature, addr in extractor.extract_insn_features(f, bb, insn)
]
instructions.append(
InstructionFeatures(
address=iaddr,
features=ifeatures,
)
)
basic_blocks.append(
BasicBlockFeatures(
address=bbaddr,
features=bbfeatures,
instructions=instructions,
)
)
function_features.append(
FunctionFeatures(
address=faddr,
features=ffeatures,
basic_blocks=basic_blocks,
)
)
features = Features(
global_=global_features,
file=file_features,
functions=function_features,
)
freeze = Freeze(
version=2,
base_address=Address.from_capa(extractor.get_base_address()),
extractor=Extractor(name=extractor.__class__.__name__),
features=features,
)
return freeze.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
freeze = Freeze.parse_raw(s)
if freeze.version != 2:
raise ValueError("unsupported freeze format version: %d", freeze.version)
return null.NullFeatureExtractor(
base_address=freeze.base_address.to_capa(),
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={
f.address.to_capa(): null.FunctionFeatures(
features=[(fe.address.to_capa(), fe.feature.to_capa()) for fe in f.features],
basic_blocks={
bb.address.to_capa(): null.BasicBlockFeatures(
features=[(fe.address.to_capa(), fe.feature.to_capa()) for fe in bb.features],
instructions={
i.address.to_capa(): null.InstructionFeatures(
features=[(fe.address.to_capa(), fe.feature.to_capa()) for fe in i.features]
)
for i in bb.instructions
},
)
for bb in f.basic_blocks
},
)
for f in freeze.features.functions
},
)
MAGIC = "capa0000".encode("ascii")
def dump(extractor: capa.features.extractors.base_extractor.FeatureExtractor) -> bytes:
"""serialize the given extractor to a byte array."""
return MAGIC + zlib.compress(dumps(extractor).encode("utf-8"))
def is_freeze(buf: bytes) -> bool:
return buf[: len(MAGIC)] == MAGIC
def load(buf: bytes) -> capa.features.extractors.base_extractor.FeatureExtractor:
"""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"))
def main(argv=None):
import sys
import argparse
import capa.main
if argv is 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"})
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)
with open(args.output, "wb") as f:
f.write(dump(extractor))
return 0
if __name__ == "__main__":
import sys
sys.exit(main())

View File

@@ -0,0 +1,345 @@
import binascii
from typing import Union, Optional
from pydantic import Field, BaseModel
import capa.features.file
import capa.features.insn
import capa.features.common
import capa.features.basicblock
class FeatureModel(BaseModel):
class Config:
frozen = True
allow_population_by_field_name = True
def to_capa(self) -> capa.features.common.Feature:
if isinstance(self, OSFeature):
return capa.features.common.OS(self.os, description=self.description)
elif isinstance(self, ArchFeature):
return capa.features.common.Arch(self.arch, description=self.description)
elif isinstance(self, FormatFeature):
return capa.features.common.Format(self.format, description=self.description)
elif isinstance(self, MatchFeature):
return capa.features.common.MatchedRule(self.match, description=self.description)
elif isinstance(
self,
CharacteristicFeature,
):
return capa.features.common.Characteristic(self.characteristic, description=self.description)
elif isinstance(self, ExportFeature):
return capa.features.file.Export(self.export, description=self.description)
elif isinstance(self, ImportFeature):
return capa.features.file.Import(self.import_, description=self.description)
elif isinstance(self, SectionFeature):
return capa.features.file.Section(self.section, description=self.description)
elif isinstance(self, FunctionNameFeature):
return capa.features.file.FunctionName(self.function_name, description=self.description)
elif isinstance(self, SubstringFeature):
return capa.features.common.Substring(self.substring, description=self.description)
elif isinstance(self, RegexFeature):
return capa.features.common.Regex(self.regex, description=self.description)
elif isinstance(self, StringFeature):
return capa.features.common.String(self.string, description=self.description)
elif isinstance(self, ClassFeature):
return capa.features.common.Class(self.class_, description=self.description)
elif isinstance(self, NamespaceFeature):
return capa.features.common.Namespace(self.namespace, description=self.description)
elif isinstance(self, BasicBlockFeature):
return capa.features.basicblock.BasicBlock(description=self.description)
elif isinstance(self, APIFeature):
return capa.features.insn.API(self.api, description=self.description)
elif isinstance(self, PropertyFeature):
return capa.features.insn.Property(self.property, access=self.access, description=self.description)
elif isinstance(self, NumberFeature):
return capa.features.insn.Number(self.number, description=self.description)
elif isinstance(self, BytesFeature):
return capa.features.common.Bytes(binascii.unhexlify(self.bytes), description=self.description)
elif isinstance(self, OffsetFeature):
return capa.features.insn.Offset(self.offset, description=self.description)
elif isinstance(self, MnemonicFeature):
return capa.features.insn.Mnemonic(self.mnemonic, description=self.description)
elif isinstance(self, OperandNumberFeature):
return capa.features.insn.OperandNumber(
self.index,
self.operand_number,
description=self.description,
)
elif isinstance(self, OperandOffsetFeature):
return capa.features.insn.OperandOffset(
self.index,
self.operand_offset,
description=self.description,
)
else:
raise NotImplementedError(f"Feature.to_capa({type(self)}) not implemented")
def feature_from_capa(f: capa.features.common.Feature) -> "Feature":
if isinstance(f, capa.features.common.OS):
return OSFeature(os=f.value, description=f.description)
elif isinstance(f, capa.features.common.Arch):
return ArchFeature(arch=f.value, description=f.description)
elif isinstance(f, capa.features.common.Format):
return FormatFeature(format=f.value, description=f.description)
elif isinstance(f, capa.features.common.MatchedRule):
return MatchFeature(match=f.value, description=f.description)
elif isinstance(f, capa.features.common.Characteristic):
return CharacteristicFeature(characteristic=f.value, description=f.description)
elif isinstance(f, capa.features.file.Export):
return ExportFeature(export=f.value, description=f.description)
elif isinstance(f, capa.features.file.Import):
return ImportFeature(import_=f.value, description=f.description)
elif isinstance(f, capa.features.file.Section):
return SectionFeature(section=f.value, description=f.description)
elif isinstance(f, capa.features.file.FunctionName):
return FunctionNameFeature(function_name=f.value, description=f.description)
# must come before check for String due to inheritance
elif isinstance(f, capa.features.common.Substring):
return SubstringFeature(substring=f.value, description=f.description)
# must come before check for String due to inheritance
elif isinstance(f, capa.features.common.Regex):
return RegexFeature(regex=f.value, description=f.description)
elif isinstance(f, capa.features.common.String):
return StringFeature(string=f.value, description=f.description)
elif isinstance(f, capa.features.common.Class):
return ClassFeature(class_=f.value, description=f.description)
elif isinstance(f, capa.features.common.Namespace):
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):
return APIFeature(api=f.value, description=f.description)
elif isinstance(f, capa.features.insn.Property):
return PropertyFeature(property=f.value, access=f.access, description=f.description)
elif isinstance(f, capa.features.insn.Number):
return NumberFeature(number=f.value, description=f.description)
elif isinstance(f, capa.features.common.Bytes):
buf = f.value
assert isinstance(buf, bytes)
return BytesFeature(bytes=binascii.hexlify(buf).decode("ascii"), description=f.description)
elif isinstance(f, capa.features.insn.Offset):
return OffsetFeature(offset=f.value, description=f.description)
elif isinstance(f, capa.features.insn.Mnemonic):
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)
elif isinstance(f, capa.features.insn.OperandOffset):
return OperandOffsetFeature(index=f.index, operand_offset=f.value, description=f.description)
else:
raise NotImplementedError(f"feature_from_capa({type(f)}) not implemented")
class OSFeature(FeatureModel):
type: str = "os"
os: str
description: Optional[str]
class ArchFeature(FeatureModel):
type: str = "arch"
arch: str
description: Optional[str]
class FormatFeature(FeatureModel):
type: str = "format"
format: str
description: Optional[str]
class MatchFeature(FeatureModel):
type: str = "match"
match: str
description: Optional[str]
class CharacteristicFeature(FeatureModel):
type: str = "characteristic"
characteristic: str
description: Optional[str]
class ExportFeature(FeatureModel):
type: str = "export"
export: str
description: Optional[str]
class ImportFeature(FeatureModel):
type: str = "import"
import_: str = Field(alias="import")
description: Optional[str]
class SectionFeature(FeatureModel):
type: str = "section"
section: str
description: Optional[str]
class FunctionNameFeature(FeatureModel):
type: str = "function name"
function_name: str = Field(alias="function name")
description: Optional[str]
class SubstringFeature(FeatureModel):
type: str = "substring"
substring: str
description: Optional[str]
class RegexFeature(FeatureModel):
type: str = "regex"
regex: str
description: Optional[str]
class StringFeature(FeatureModel):
type: str = "string"
string: str
description: Optional[str]
class ClassFeature(FeatureModel):
type: str = "class"
class_: str = Field(alias="class")
description: Optional[str]
class NamespaceFeature(FeatureModel):
type: str = "namespace"
namespace: str
description: Optional[str]
class BasicBlockFeature(FeatureModel):
type: str = "basic block"
description: Optional[str]
class APIFeature(FeatureModel):
type: str = "api"
api: str
description: Optional[str]
class PropertyFeature(FeatureModel):
type: str = "property"
access: Optional[str]
property: str
description: Optional[str]
class NumberFeature(FeatureModel):
type: str = "number"
number: Union[int, float]
description: Optional[str]
class BytesFeature(FeatureModel):
type: str = "bytes"
bytes: str
description: Optional[str]
class OffsetFeature(FeatureModel):
type: str = "offset"
offset: int
description: Optional[str]
class MnemonicFeature(FeatureModel):
type: str = "mnemonic"
mnemonic: str
description: Optional[str]
class OperandNumberFeature(FeatureModel):
type: str = "operand number"
index: int
operand_number: int = Field(alias="operand number")
description: Optional[str]
class OperandOffsetFeature(FeatureModel):
type: str = "operand offset"
index: int
operand_offset: int = Field(alias="operand offset")
description: Optional[str]
Feature = Union[
OSFeature,
ArchFeature,
FormatFeature,
MatchFeature,
CharacteristicFeature,
ExportFeature,
ImportFeature,
SectionFeature,
FunctionNameFeature,
SubstringFeature,
RegexFeature,
StringFeature,
ClassFeature,
NamespaceFeature,
APIFeature,
PropertyFeature,
NumberFeature,
BytesFeature,
OffsetFeature,
MnemonicFeature,
OperandNumberFeature,
OperandOffsetFeature,
# Note! this must be last, see #1161
BasicBlockFeature,
]

View File

@@ -1,40 +1,131 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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
from typing import Union, Optional
from capa.features import Feature
import capa.helpers
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)
else:
return "0x%X" % n
class API(Feature):
def __init__(self, name, description=None):
# Downcase library name if given
if "." in name:
modname, impname = name.split(".")
name = modname.lower() + "." + impname
def __init__(self, name: str, description=None):
super().__init__(name, description=description)
super(API, self).__init__(name, description)
class _AccessFeature(Feature, abc.ABC):
# superclass: don't use directly
def __init__(self, value: str, access: Optional[str] = None, description: Optional[str] = None):
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))
self.access = access
def __hash__(self):
return hash((self.name, self.value, self.access))
def __eq__(self, other):
return super().__eq__(other) and self.access == other.access
def get_name_str(self) -> str:
if self.access is not None:
return f"{self.name}/{self.access}"
return self.name
class Property(_AccessFeature):
def __init__(self, value: str, access: Optional[str] = None, description=None):
super().__init__(value, access=access, description=description)
class Number(Feature):
def __init__(self, value, arch=None, description=None):
super(Number, self).__init__(value, arch=arch, description=description)
def __init__(self, value: Union[int, float], description=None):
super().__init__(value, description=description)
def get_value_str(self):
return "0x%X" % 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")
# max recognized structure size (and therefore, offset size)
MAX_STRUCTURE_SIZE = 0x10000
class Offset(Feature):
def __init__(self, value, arch=None, description=None):
super(Offset, self).__init__(value, arch=arch, description=description)
def __init__(self, value: int, description=None):
super().__init__(value, description=description)
def get_value_str(self):
return "0x%X" % self.value
assert isinstance(self.value, int)
return hex(self.value)
class Mnemonic(Feature):
def __init__(self, value, description=None):
super(Mnemonic, self).__init__(value, description=description)
def __init__(self, value: str, description=None):
super().__init__(value, description=description)
# max number of operands to consider for a given instruction.
# since we only support Intel and .NET, we can assume this is 3
# which covers cases up to e.g. "vinserti128 ymm0,ymm0,ymm5,1"
MAX_OPERAND_COUNT = 4
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):
super().__init__(value, description=description)
self.index = index
def __hash__(self):
return hash((self.name, self.value))
def __eq__(self, other):
return super().__eq__(other) and self.index == other.index
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)]
# operand[i].number: 0x12
def __init__(self, index: int, value: int, description=None):
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)
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)]
# operand[i].offset: 0x12
def __init__(self, index: int, value: int, description=None):
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)

View File

@@ -1,36 +1,126 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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 logging
from typing import NoReturn
_hex = hex
from capa.exceptions import UnsupportedFormatError
from capa.features.common import FORMAT_PE, FORMAT_SC32, FORMAT_SC64, FORMAT_DOTNET, FORMAT_UNKNOWN, Format
EXTENSIONS_SHELLCODE_32 = ("sc32", "raw32")
EXTENSIONS_SHELLCODE_64 = ("sc64", "raw64")
EXTENSIONS_ELF = "elf_"
logger = logging.getLogger("capa")
def hex(i):
# under py2.7, long integers get formatted with a trailing `L`
# and this is not pretty. so strip it out.
return _hex(oint(i)).rstrip("L")
def hex(n: int) -> str:
"""render the given number using upper case hex, like: 0x123ABC"""
if n < 0:
return "-0x%X" % (-n)
else:
return "0x%X" % n
def oint(i):
# there seems to be some trouble with using `int(viv_utils.Function)`
# with the black magic we do with binding the `__int__()` routine.
# i haven't had a chance to debug this yet (and i have no hotel wifi).
# so in the meantime, detect this, and call the method directly.
try:
return int(i)
except TypeError:
return i.__int__()
def get_file_taste(sample_path):
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)
return taste
def is_runtime_ida():
try:
import idc
except ImportError:
return False
else:
return True
def assert_never(value: NoReturn) -> NoReturn:
assert False, f"Unhandled value: {value} ({type(value).__name__})"
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
return FORMAT_UNKNOWN
def get_auto_format(path: str) -> str:
format_ = get_format(path)
if format_ == FORMAT_UNKNOWN:
format_ = get_format_from_extension(path)
if format_ == FORMAT_UNKNOWN:
raise UnsupportedFormatError()
return format_
def get_format(sample: str) -> str:
# imported locally to avoid import cycle
from capa.features.extractors.common import extract_format
from capa.features.extractors.dnfile_ import DnfileFeatureExtractor
with open(sample, "rb") as f:
buf = f.read()
for feature, _ in extract_format(buf):
if feature == Format(FORMAT_PE):
dnfile_extractor = DnfileFeatureExtractor(sample)
if dnfile_extractor.is_dotnet_file():
feature = Format(FORMAT_DOTNET)
assert isinstance(feature.value, str)
return feature.value
return FORMAT_UNKNOWN
def log_unsupported_format_error():
logger.error("-" * 80)
logger.error(" Input file does not appear to be a PE or ELF file.")
logger.error(" ")
logger.error(
" capa currently only supports analyzing PE and ELF files (or shellcode, when using --format sc32|sc64)."
)
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_os_error():
logger.error("-" * 80)
logger.error(" Input file does not appear to target a supported OS.")
logger.error(" ")
logger.error(
" capa currently only supports analyzing executables for some operating systems (including Windows and Linux)."
)
logger.error("-" * 80)
def log_unsupported_arch_error():
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)
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(" ")
logger.error(
" If you're seeing this message on the command line, please ensure you're running a supported Python version."
)
logger.error("-" * 80)

252
capa/ida/helpers.py Normal file
View File

@@ -0,0 +1,252 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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
import idc
import idaapi
import idautils
import ida_bytes
import ida_loader
from netnode import netnode
import capa
import capa.version
import capa.render.utils as rutils
import capa.features.common
import capa.render.result_document
from capa.features.address import AbsoluteVirtualAddress
logger = logging.getLogger("capa")
# file type as returned by idainfo.file_type
SUPPORTED_FILE_TYPES = (
idaapi.f_PE,
idaapi.f_ELF,
idaapi.f_BIN,
idaapi.f_COFF,
# idaapi.f_MACHO,
)
# arch type as returned by idainfo.procname
SUPPORTED_ARCH_TYPES = ("metapc",)
CAPA_NETNODE = f"$ com.mandiant.capa.v{capa.version.__version__}"
NETNODE_RESULTS = "results"
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)
def is_supported_ida_version():
version = float(idaapi.get_kernel_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)
return False
return True
def is_supported_file_type():
file_info = idaapi.get_inf_structure()
if file_info.filetype 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():
file_info = idaapi.get_inf_structure()
if file_info.procname not in SUPPORTED_ARCH_TYPES or not any((file_info.is_32bit(), file_info.is_64bit())):
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_disasm_line(va):
""" """
return idc.generate_disasm_line(va, idc.GENDSM_FORCE_CODE)
def is_func_start(ea):
"""check if function stat exists at virtual address"""
f = idaapi.get_func(ea)
return f and f.start_ea == ea
def get_func_start_ea(ea):
""" """
f = idaapi.get_func(ea)
return f if f is None else f.start_ea
def get_file_md5():
""" """
md5 = idautils.GetInputFileMD5()
if not isinstance(md5, str):
md5 = capa.features.common.bytes_to_str(md5)
return md5
def get_file_sha256():
""" """
sha256 = idaapi.retrieve_input_file_sha256()
if not isinstance(sha256, str):
sha256 = capa.features.common.bytes_to_str(sha256)
return sha256
def collect_metadata(rules):
""" """
md5 = get_file_md5()
sha256 = get_file_sha256()
info: idaapi.idainfo = idaapi.get_inf_structure()
if info.procname == "metapc" and info.is_64bit():
arch = "x86_64"
elif info.procname == "metapc" and info.is_32bit():
arch = "x86"
else:
arch = "unknown arch"
format_name: str = ida_loader.get_file_type_name()
if "PE" in format_name:
os = "windows"
elif "ELF" in format_name:
with contextlib.closing(capa.ida.helpers.IDAIO()) as f:
os = capa.features.extractors.elf.detect_elf_os(f)
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": {
# 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__,
}
class IDAIO:
"""
An object that acts as a file-like object,
using bytes from the current IDB workspace.
"""
def __init__(self):
super().__init__()
self.offset = 0
def seek(self, offset, whence=0):
assert whence == 0
self.offset = offset
def read(self, size):
ea = ida_loader.get_fileregion_ea(self.offset)
if ea == idc.BADADDR:
logger.debug("cannot read 0x%x bytes at 0x%x (ea: BADADDR)", size, self.offset)
return b""
logger.debug("reading 0x%x bytes at 0x%x (ea: 0x%x)", size, self.offset, ea)
# get_bytes returns None on error, for consistency with read always return bytes
return ida_bytes.get_bytes(ea, size) or b""
def close(self):
return
def save_cached_results(resdoc):
logger.debug("saving cached capa results to netnode '%s'", CAPA_NETNODE)
n = netnode.Netnode(CAPA_NETNODE)
n[NETNODE_RESULTS] = resdoc.json()
def idb_contains_cached_results() -> bool:
try:
n = netnode.Netnode(CAPA_NETNODE)
return bool(n.get(NETNODE_RESULTS))
except netnode.NetnodeCorruptError as e:
logger.error("%s", e, exc_info=True)
return False
def load_and_verify_cached_results() -> Optional[capa.render.result_document.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]))
for rule in rutils.capability_rules(doc):
for location_, _ in rule.matches:
location = location_.to_capa()
if isinstance(location, AbsoluteVirtualAddress):
ea = int(location)
if not idaapi.is_mapped(ea):
logger.error("cached address %s is not a valid location in this database", hex(ea))
return None
return doc
def save_rules_cache_id(ruleset_id):
logger.debug("saving ruleset ID to netnode '%s'", CAPA_NETNODE)
n = netnode.Netnode(CAPA_NETNODE)
n[NETNODE_RULES_CACHE_ID] = ruleset_id
def load_rules_cache_id():
n = netnode.Netnode(CAPA_NETNODE)
return n[NETNODE_RULES_CACHE_ID]
def delete_cached_results():
logger.debug("deleting cached capa data")
n = netnode.Netnode(CAPA_NETNODE)
del n[NETNODE_RESULTS]

View File

@@ -1,108 +0,0 @@
# Copyright (C) 2020 FireEye, 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 idc
import six
import idaapi
import idautils
import capa
logger = logging.getLogger("capa")
SUPPORTED_IDA_VERSIONS = [
"7.1",
"7.2",
"7.3",
"7.4",
"7.5",
]
# file type names as returned by idaapi.get_file_type_name()
SUPPORTED_FILE_TYPES = [
"Portable executable for 80386 (PE)",
"Portable executable for AMD64 (PE)",
"Binary file", # x86/AMD64 shellcode support
]
def inform_user_ida_ui(message):
idaapi.info("%s. Please refer to IDA Output window for more information." % message)
def is_supported_ida_version():
version = idaapi.get_kernel_version()
if version not in SUPPORTED_IDA_VERSIONS:
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: %s." % (version, ", ".join(SUPPORTED_IDA_VERSIONS))
)
return False
return True
def is_supported_file_type():
file_type = idaapi.get_file_type_name()
if file_type not in SUPPORTED_FILE_TYPES:
logger.error("-" * 80)
logger.error(" Input file does not appear to be a PE file.")
logger.error(" ")
logger.error(
" capa currently only supports analyzing PE files (or binary files containing x86/AMD64 shellcode) with IDA."
)
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 get_disasm_line(va):
""" """
return idc.generate_disasm_line(va, idc.GENDSM_FORCE_CODE)
def is_func_start(ea):
""" check if function stat exists at virtual address """
f = idaapi.get_func(ea)
return f and f.start_ea == ea
def get_func_start_ea(ea):
""" """
f = idaapi.get_func(ea)
return f if f is None else f.start_ea
def collect_metadata():
md5 = idautils.GetInputFileMD5()
if not isinstance(md5, six.string_types):
md5 = capa.features.bytes_to_str(md5)
sha256 = idaapi.retrieve_input_file_sha256()
if not isinstance(sha256, six.string_types):
sha256 = capa.features.bytes_to_str(sha256)
return {
"timestamp": datetime.datetime.now().isoformat(),
# "argv" is not relevant here
"sample": {
"md5": md5,
"sha1": "", # not easily accessible
"sha256": sha256,
"path": idaapi.get_input_file_path(),
},
"analysis": {
"format": idaapi.get_file_type_name(),
"extractor": "ida",
},
"version": capa.version.__version__,
}

View File

@@ -1,111 +1,126 @@
# capa explorer
![capa explorer](../../../.github/capa-explorer-logo.png)
capa explorer is an IDA Pro plugin that integrates the FLARE team's open-source framework, capa, with IDA. 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 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.
capa explorer is an IDAPython plugin that integrates the FLARE team's open-source framework, capa, with IDA Pro. 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. capa explorer runs capa analysis on your IDA Pro database (IDB) without needing access
to the original binary file. Once a database has been analyzed, capa explorer helps you identify interesting areas of a program and build new capa rules using features extracted from your IDB.
The capa explorer IDA plugin brings capa's detection capabilities to IDA. You can use capa explorer to run capa directly on an IDA database without needing access
to the source binary. Once a database has been analyzed, capa explorer can be used to quickly identify and navigate to interesting areas of a program
and dissect capa rule matches at the assembly level.
To illustrate, we use capa explorer to analyze Lab 14-02 from [Practical Malware Analysis](https://nostarch.com/malware) (PMA) available [here](https://practicalmalwareanalysis.com/labs/). Our
goal is to understand the program's functionality.
We love using capa explorer during malware analysis because it teaches us what parts of a program suggest a behavior. As we click on rows, capa explorer jumps directly
to important addresses in the IDB and highlights key features in the Disassembly view so they stand out visually. To illustrate, we use capa explorer to
analyze Lab 14-02 from [Practical Malware Analysis](https://nostarch.com/malware) (PMA) available [here](https://practicalmalwareanalysis.com/labs/). Our goal is to understand
the program's functionality.
After loading Lab 14-02 into IDA and analyzing the database with capa explorer, we see that capa detected a rule match for `self delete via COMSPEC environment variable`:
![](../../../doc/img/ida_plugin_example_1.png)
![](../../../doc/img/explorer_condensed.png)
We can use capa explorer to navigate the IDA Disassembly view directly to the suspect function and get an assembly-level breakdown of why capa matched `self delete via COMSPEC environment variable`
for this particular function.
We can use capa explorer to navigate our Disassembly view directly to the suspect function and get an assembly-level breakdown of why capa matched `self delete via COMSPEC environment variable`.
![](../../../doc/img/ida_plugin_example_2.png)
![](../../../doc/img/explorer_expanded.png)
Using the `Rule Information` and `Details` columns capa explorer shows us that the suspect function matched `self delete via COMSPEC environment variable` because it contains capa rule matches for `create process`, `get COMSPEC environment variable`,
and `query environment variable`, references to the strings `COMSPEC`, ` > nul`, and `/c del`, and calls to the Windows API functions `GetEnvironmentVariableA` and `ShellExecuteEx`.
and `query environment variable`, references to the strings `COMSPEC`, ` > nul`, and `/c del `, and calls to the Windows API functions `GetEnvironmentVariableA` and `ShellExecuteEx`.
For more information on the FLARE team's open-source framework, capa, check out the overview in our first [blog](https://www.fireeye.com/blog/threat-research/2020/07/capa-automatically-identify-malware-capabilities.html).
capa explorer also helps you build and test new capa rules. To start, select the `Rule Generator` tab, navigate to a function in your Disassembly view,
and click `Analyze`. capa explorer will extract features from the function and display them in the `Features` pane. You can add features listed in this pane to the `Editor` pane
by either double-clicking a feature or using multi-select + right-click to add multiple features at once. The `Preview` and `Editor` panes help edit your rule. Use the `Preview` pane
to modify rule text directly and the `Editor` pane to construct and rearrange your hierarchy of statements and features. When you finish a rule you can save it directly to a file by clicking `Save`.
## Features
![](../../../doc/img/rulegen_expanded.png)
![](../../../doc/img/ida_plugin_intro.gif)
* Display capa results in an interactive tree view of rule matches and their locations in the current database
* Search for keywords or phrases found in the `Rule Information`, `Address`, or `Details` columns
* Display rule source content when a user hovers their cursor over a rule match
* Double-click `Address` column to view associated feature in the IDA Disassembly view
* Limit tree view results to the function currently displayed in the IDA Disassembly view; update results as a user navigates to different functions
* Export results as formatted JSON by navigating to `File > Export results...`
* Remember a user's capa rules directory for future runs; change capa rules directory by navigating to `Rules > Change rules directory...`
* Automatically re-analyze database when user performs a program rebase
* Automatically update results when IDA is used to rename a function
* Select one or more checkboxes to highlight the associated addresses in the IDA Disassembly view
* Right-click a function match to rename it; the new function name is propagated to the current IDA database
* Right-click to copy a result by column or by row
* Sort results by column
* Reset tree view and IDA Disassembly view highlighting by clicking `Reset`
For more information on the FLARE team's open-source framework, capa, check out the overview in our first [blog](https://www.mandiant.com/resources/capa-automatically-identify-malware-capabilities).
## Getting Started
### Requirements
capa explorer supports the following IDA setups:
* IDA Pro 7.4+ with Python 2.7 or Python 3.
If you encounter issues with your specific setup, please open a new [Issue](https://github.com/fireeye/capa/issues).
### Supported File Types
capa explorer is limited to the file types supported by capa, which includes:
* Windows 32-bit and 64-bit PE files
* Windows 32-bit and 64-bit shellcode
### Installation
You can install capa explorer using the following steps:
1. Install capa for the Python interpreter used by your IDA installation:
1. Install capa and its dependencies from PyPI using the Python interpreter configured for your IDA installation:
```
$ pip install flare-capa
```
3. Download the [standard collection of capa rules](https://github.com/fireeye/capa-rules) (capa explorer needs capa rules to analyze a database)
4. Copy [capa_explorer.py](https://raw.githubusercontent.com/fireeye/capa/master/capa/ida/plugin/capa_explorer.py) to your IDA plugins directory
2. Download and extract the [official capa rules](https://github.com/mandiant/capa-rules/releases) that match the version of capa you have installed
1. Use the following command to view the version of capa you have installed:
```commandline
$ pip show flare-capa
OR
$ capa --version
```
3. Copy [capa_explorer.py](https://raw.githubusercontent.com/mandiant/capa/master/capa/ida/plugin/capa_explorer.py) to your IDA plugins directory
- find your plugin directories via `idaapi.get_ida_subdirs("plugins")` or see this [Hex-Rays blog](https://hex-rays.com/blog/igors-tip-of-the-week-103-sharing-plugins-between-ida-installs/)
- common paths are `%APPDATA%\Hex-Rays\IDA Pro\plugins` (Windows) or `$HOME/.idapro/plugins` on Linux/Mac
### Supported File Types
capa explorer is limited to the file types supported by capa, which include:
* Windows x86 (32- and 64-bit) PE files
* Windows x86 (32- and 64-bit) shellcode
* ELF files on various operating systems
### Usage
1. Run IDA and analyze a supported file type (select `Manual Load` and `Load Resources` for best results)
1. Open IDA and analyze a supported file type (select the `Manual Load` and `Load Resources` options in IDA for best results)
2. Open capa explorer in IDA by navigating to `Edit > Plugins > FLARE capa explorer` or using the keyboard shortcut `Alt+F5`
3. Click `Analyze`
You can also use `ida_loader.load_and_run_plugin("capa_explorer", arg)`. `arg` is a bitflag for which setting the LSB enables automatic analysis. See `capa.ida.plugin.form.Options` for more details.
3. Select the `Program Analysis` tab
4. Click the `Analyze` button
When running capa explorer for the first time you are prompted to select a file directory containing capa rules. The plugin conveniently
remembers your selection for future runs; you can change this selection by navigating to `Rules > Change rules directory...`. We recommend
downloading and using the [standard collection of capa rules](https://github.com/fireeye/capa-rules) when getting started with the plugin.
The first time you run capa explorer you will be asked to specify a local directory containing capa rules to use for analysis. We recommend downloading and extracting the [official capa rules](https://github.com/mandiant/capa-rules/releases) that match
the version of capa you have installed (see installation instructions above for more details). capa explorer remembers your selection for future analysis which you
can update using the `Settings` button.
#### Tips
#### Tips for Program Analysis
* Start analysis by clicking `Analyze`
* Reset the plugin user interface and remove highlighting from IDA disassembly view by clicking `Reset`
* Change your capa rules directory by navigating to `Rules > Change rules directory...`
* Start analysis by clicking the `Analyze` button
* capa explorer caches results to the database and reuses them across IDA sessions
* Reset the plugin user interface and remove highlighting from your Disassembly view by clicking the `Reset` button
* Change your local capa rules directory, auto analysis settings, and other default settings by clicking the `Settings` button
* Hover your cursor over a rule match to view the source content of the rule
* Double-click the `Address` column to navigate the IDA Disassembly view to the associated feature
* Double-click the `Address` column to navigate your Disassembly view to the address of the associated feature
* Double-click a result in the `Rule Information` column to expand its children
* Select a checkbox in the `Rule Information` column to highlight the address of the associated feature in the IDA Dissasembly view
* Select a checkbox in the `Rule Information` column to highlight the address of the associated feature in your Disassembly view
#### Tips for Rule Generator
* Navigate to a function in your Disassembly view and click`Analyze` to get started
* Double-click or use multi-select + right-click to add features from the `Features` pane to the `Editor` pane
* Right-click features in the `Editor` pane to make context-specific modifications
* Drag-and-drop (single click + multi-select support) features in the `Editor` pane to construct your hierarchy of statements and features
* Right-click anywhere in the `Editor` pane not on a feature to remove all features
* Add descriptions or comments to a feature by editing the corresponding column in the `Editor` pane
* Directly edit rule text and metadata fields using the `Preview` pane
* Change the default rule author and default rule scope displayed in the `Preview` pane by clicking `Settings`
### Requirements
capa explorer supports Python versions >= 3.7.x and IDA Pro versions >= 7.4. The following IDA Pro versions have been tested:
* IDA 7.4
* IDA 7.5
* IDA 7.6 Service Pack 1
* IDA 7.7
* IDA 8.0
* IDA 8.1
* IDA 8.2
capa explorer is however limited to the Python versions supported by your IDA installation (which may not include all Python versions >= 3.7.x).
If you encounter issues with your specific setup, please open a new [Issue](https://github.com/mandiant/capa/issues).
## Development
Because capa explorer is packaged with capa you will need to install capa locally for development.
You can install capa locally by following the steps outlined in `Method 3: Inspecting the capa source code` of the [capa
installation guide](https://github.com/fireeye/capa/blob/ida_plugin_documentation/doc/installation.md#method-3-inspecting-the-capa-source-code). Once installed, copy [capa_explorer.py](https://raw.githubusercontent.com/fireeye/capa/master/capa/ida/plugin/capa_explorer.py)
to your IDA plugins directory to run the plugin in IDA.
capa explorer is packaged with capa so you will need to install capa locally for development. You can install capa locally by following the steps outlined in `Method 3: Inspecting the capa source code` of the [capa
installation guide](https://github.com/mandiant/capa/blob/master/doc/installation.md#method-3-inspecting-the-capa-source-code). Once installed, copy [capa_explorer.py](https://raw.githubusercontent.com/mandiant/capa/master/capa/ida/plugin/capa_explorer.py)
to your plugins directory to install capa explorer in IDA.
### Components
capa explorer consists of two main components:
* An IDA [feature extractor](https://github.com/fireeye/capa/tree/master/capa/features/extractors/ida) built on top of IDA's binary analysis engine
* This component uses IDAPython to extract [capa features](https://github.com/fireeye/capa-rules/blob/master/doc/format.md#extracted-features) from the IDA database such as strings,
* An [feature extractor](https://github.com/mandiant/capa/tree/master/capa/features/extractors/ida) built on top of IDA's binary analysis engine
* This component uses IDAPython to extract [capa features](https://github.com/mandiant/capa-rules/blob/master/doc/format.md#extracted-features) from your IDBs such as strings,
disassembly, and control flow; these extracted features are used by capa to find feature combinations that result in a rule match
* An [interactive plugin](https://github.com/fireeye/capa/tree/master/capa/ida/plugin) for displaying and exploring capa rule matches
* This component integrates the IDA feature extractor and capa, providing an interactive user interface to dissect rule matches found by capa using features extracted by the IDA feature extractor
* An [interactive user interface](https://github.com/mandiant/capa/tree/master/capa/ida/plugin) for displaying and exploring capa rule matches
* This component integrates the feature extractor and capa, providing an interactive user interface to dissect rule matches found by capa using features extracted directly from your IDBs

View File

@@ -1,17 +1,15 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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 idaapi
import ida_kernwin
from capa.ida.helpers import is_supported_file_type, is_supported_ida_version
from capa.ida.plugin.form import CapaExplorerForm
from capa.ida.plugin.icon import ICON
@@ -19,7 +17,6 @@ logger = logging.getLogger(__name__)
class CapaExplorerPlugin(idaapi.plugin_t):
# Mandatory definitions
PLUGIN_NAME = "FLARE capa explorer"
PLUGIN_VERSION = "1.0.0"
@@ -28,8 +25,8 @@ class CapaExplorerPlugin(idaapi.plugin_t):
wanted_name = PLUGIN_NAME
wanted_hotkey = "ALT-F5"
comment = "IDA Pro plugin for the FLARE team's capa tool to identify capabilities in executable files."
website = "https://github.com/fireeye/capa"
help = "See https://github.com/fireeye/capa/blob/master/doc/usage.md"
website = "https://github.com/mandiant/capa"
help = "See https://github.com/mandiant/capa/blob/master/doc/usage.md"
version = ""
flags = 0
@@ -41,10 +38,14 @@ class CapaExplorerPlugin(idaapi.plugin_t):
"""called when IDA is loading the plugin"""
logging.basicConfig(level=logging.INFO)
import capa.ida.helpers
# do not load plugin if IDA version/file type not supported
if not is_supported_ida_version():
if not capa.ida.helpers.is_supported_ida_version():
return idaapi.PLUGIN_SKIP
if not is_supported_file_type():
if not capa.ida.helpers.is_supported_file_type():
return idaapi.PLUGIN_SKIP
if not capa.ida.helpers.is_supported_arch_type():
return idaapi.PLUGIN_SKIP
return idaapi.PLUGIN_OK
@@ -53,8 +54,14 @@ class CapaExplorerPlugin(idaapi.plugin_t):
pass
def run(self, arg):
"""called when IDA is running the plugin as a script"""
self.form = CapaExplorerForm(self.PLUGIN_NAME)
"""
called when IDA is running the plugin as a script
args:
arg (int): bitflag. Setting LSB enables automatic analysis upon
loading. The other bits are currently undefined. See `form.Options`.
"""
self.form = CapaExplorerForm(self.PLUGIN_NAME, arg)
return True
@@ -77,7 +84,7 @@ class CapaExplorerPlugin(idaapi.plugin_t):
# so we need to register a callback that's invoked from the main thread after the plugin is registered.
#
# after a lot of guess-and-check, we can use `UI_Hooks.updated_actions` to
# receive notications after IDA has created an action for each plugin.
# receive notifications after IDA has created an action for each plugin.
# so, create this hook, wait for capa plugin to load, set the icon, and unhook.
@@ -85,7 +92,7 @@ class OnUpdatedActionsHook(ida_kernwin.UI_Hooks):
"""register a callback to be invoked each time the UI actions are updated"""
def __init__(self, cb):
super(OnUpdatedActionsHook, self).__init__()
super().__init__()
self.cb = cb
def updated_actions(self):

220
capa/ida/plugin/cache.py Normal file
View File

@@ -0,0 +1,220 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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 __future__ import annotations
import itertools
import collections
from typing import Set, Dict, List, Tuple, Union, Optional
import capa.engine
from capa.rules import Scope, RuleSet
from capa.engine import FeatureSet, MatchResults
from capa.features.address import NO_ADDRESS, Address
from capa.ida.plugin.extractor import CapaExplorerFeatureExtractor
from capa.features.extractors.base_extractor import BBHandle, InsnHandle, FunctionHandle
class CapaRuleGenFeatureCacheNode:
def __init__(
self,
inner: Optional[Union[FunctionHandle, BBHandle, InsnHandle]],
parent: Optional[CapaRuleGenFeatureCacheNode],
):
self.inner: Optional[Union[FunctionHandle, BBHandle, InsnHandle]] = inner
self.address = NO_ADDRESS if self.inner is None else self.inner.address
self.parent: Optional[CapaRuleGenFeatureCacheNode] = parent
if self.parent is not None:
self.parent.children.add(self)
self.features: FeatureSet = collections.defaultdict(set)
self.children: Set[CapaRuleGenFeatureCacheNode] = set()
def __hash__(self):
# TODO: unique enough?
return hash((self.address,))
def __eq__(self, other):
if not isinstance(other, type(self)):
return NotImplemented
# TODO: unique enough?
return self.address == other.address
class CapaRuleGenFeatureCache:
def __init__(self, fh_list: List[FunctionHandle], extractor: CapaExplorerFeatureExtractor):
self.global_features: FeatureSet = collections.defaultdict(set)
self.file_node: CapaRuleGenFeatureCacheNode = CapaRuleGenFeatureCacheNode(None, None)
self.func_nodes: Dict[Address, CapaRuleGenFeatureCacheNode] = {}
self.bb_nodes: Dict[Address, CapaRuleGenFeatureCacheNode] = {}
self.insn_nodes: Dict[Address, CapaRuleGenFeatureCacheNode] = {}
self._find_global_features(extractor)
self._find_file_features(extractor)
self._find_function_and_below_features(fh_list, extractor)
def _find_global_features(self, extractor: CapaExplorerFeatureExtractor):
for feature, addr in extractor.extract_global_features():
# not all global 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 addr is not None:
self.global_features[feature].add(addr)
else:
if feature not in self.global_features:
self.global_features[feature] = set()
def _find_file_features(self, extractor: CapaExplorerFeatureExtractor):
# 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.
for feature, addr in extractor.extract_file_features():
if addr is not None:
self.file_node.features[feature].add(addr)
else:
if feature not in self.file_node.features:
self.file_node.features[feature] = set()
def _find_function_and_below_features(self, fh_list: List[FunctionHandle], extractor: CapaExplorerFeatureExtractor):
for fh in fh_list:
f_node: CapaRuleGenFeatureCacheNode = CapaRuleGenFeatureCacheNode(fh, self.file_node)
# extract basic block and below features
for bbh in extractor.get_basic_blocks(fh):
bb_node: CapaRuleGenFeatureCacheNode = CapaRuleGenFeatureCacheNode(bbh, f_node)
# extract instruction features
for ih in extractor.get_instructions(fh, bbh):
inode: CapaRuleGenFeatureCacheNode = CapaRuleGenFeatureCacheNode(ih, bb_node)
for feature, addr in extractor.extract_insn_features(fh, bbh, ih):
inode.features[feature].add(addr)
self.insn_nodes[inode.address] = inode
# extract basic block features
for feature, addr in extractor.extract_basic_block_features(fh, bbh):
bb_node.features[feature].add(addr)
# store basic block features in cache and function parent
self.bb_nodes[bb_node.address] = bb_node
# extract function features
for feature, addr in extractor.extract_function_features(fh):
f_node.features[feature].add(addr)
self.func_nodes[f_node.address] = f_node
def _find_instruction_capabilities(
self, ruleset: RuleSet, insn: CapaRuleGenFeatureCacheNode
) -> Tuple[FeatureSet, MatchResults]:
features: FeatureSet = collections.defaultdict(set)
for feature, locs in itertools.chain(insn.features.items(), self.global_features.items()):
features[feature].update(locs)
_, matches = ruleset.match(Scope.INSTRUCTION, features, insn.address)
for name, result in matches.items():
rule = ruleset[name]
for addr, _ in result:
capa.engine.index_rule_matches(features, rule, [addr])
return features, matches
def _find_basic_block_capabilities(
self, ruleset: RuleSet, bb: CapaRuleGenFeatureCacheNode
) -> Tuple[FeatureSet, MatchResults, MatchResults]:
features: FeatureSet = collections.defaultdict(set)
insn_matches: MatchResults = collections.defaultdict(list)
for insn in bb.children:
ifeatures, imatches = self._find_instruction_capabilities(ruleset, insn)
for feature, locs in ifeatures.items():
features[feature].update(locs)
for name, result in imatches.items():
insn_matches[name].extend(result)
for feature, locs in itertools.chain(bb.features.items(), self.global_features.items()):
features[feature].update(locs)
_, matches = ruleset.match(Scope.BASIC_BLOCK, features, bb.address)
for name, result in matches.items():
rule = ruleset[name]
for loc, _ in result:
capa.engine.index_rule_matches(features, rule, [loc])
return features, matches, insn_matches
def find_code_capabilities(
self, ruleset: RuleSet, fh: FunctionHandle
) -> Tuple[FeatureSet, MatchResults, MatchResults, MatchResults]:
f_node: Optional[CapaRuleGenFeatureCacheNode] = self.func_nodes.get(fh.address, None)
if f_node is None:
return {}, {}, {}, {}
insn_matches: MatchResults = collections.defaultdict(list)
bb_matches: MatchResults = collections.defaultdict(list)
function_features: FeatureSet = collections.defaultdict(set)
for bb in f_node.children:
features, bmatches, imatches = self._find_basic_block_capabilities(ruleset, bb)
for feature, locs in features.items():
function_features[feature].update(locs)
for name, result in bmatches.items():
bb_matches[name].extend(result)
for name, result in imatches.items():
insn_matches[name].extend(result)
for feature, locs in itertools.chain(f_node.features.items(), self.global_features.items()):
function_features[feature].update(locs)
_, function_matches = ruleset.match(Scope.FUNCTION, function_features, f_node.address)
return function_features, function_matches, bb_matches, insn_matches
def find_file_capabilities(self, ruleset: RuleSet) -> Tuple[FeatureSet, MatchResults]:
features: FeatureSet = collections.defaultdict(set)
for func_node in self.file_node.children:
assert func_node.inner is not None
assert isinstance(func_node.inner, FunctionHandle)
func_features, _, _, _ = self.find_code_capabilities(ruleset, func_node.inner)
for feature, locs in func_features.items():
features[feature].update(locs)
for feature, locs in itertools.chain(self.file_node.features.items(), self.global_features.items()):
features[feature].update(locs)
_, matches = ruleset.match(Scope.FILE, features, NO_ADDRESS)
return features, matches
def get_all_function_features(self, fh: FunctionHandle) -> FeatureSet:
f_node: Optional[CapaRuleGenFeatureCacheNode] = self.func_nodes.get(fh.address, None)
if f_node is None:
return {}
all_function_features: FeatureSet = collections.defaultdict(set)
all_function_features.update(f_node.features)
for bb_node in f_node.children:
for i_node in bb_node.children:
for feature, locs in i_node.features.items():
all_function_features[feature].update(locs)
for feature, locs in bb_node.features.items():
all_function_features[feature].update(locs)
# include global features just once
for feature, locs in self.global_features.items():
all_function_features[feature].update(locs)
return all_function_features
def get_all_file_features(self):
yield from itertools.chain(self.file_node.features.items(), self.global_features.items())

View File

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

13
capa/ida/plugin/error.py Normal file
View File

@@ -0,0 +1,13 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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 UserCancelledError(Exception):
"""throw exception when user cancels action"""
pass

View File

@@ -0,0 +1,44 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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 ida_kernwin
from PyQt5 import QtCore
from capa.ida.plugin.error import UserCancelledError
from capa.features.extractors.ida.extractor import IdaFeatureExtractor
from capa.features.extractors.base_extractor import FunctionHandle
class CapaExplorerProgressIndicator(QtCore.QObject):
"""implement progress signal, used during feature extraction"""
progress = QtCore.pyqtSignal(str)
def update(self, text):
"""emit progress update
check if user cancelled action, raise exception for parent function to catch
"""
if ida_kernwin.user_cancelled():
raise UserCancelledError("user cancelled")
self.progress.emit("extracting features from %s" % text)
class CapaExplorerFeatureExtractor(IdaFeatureExtractor):
"""subclass the IdaFeatureExtractor
track progress during feature extraction, also allow user to cancel feature extraction
"""
def __init__(self):
super().__init__()
self.indicator = CapaExplorerProgressIndicator()
def extract_function_features(self, fh: FunctionHandle):
self.indicator.update("function at 0x%X" % fh.inner.start_ea)
return super().extract_function_features(fh)

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
@@ -16,7 +16,7 @@ class CapaExplorerIdaHooks(idaapi.UI_Hooks):
@param screen_ea_changed_hook: function hook for IDA screen ea changed
@param action_hooks: dict of IDA action handles
"""
super(CapaExplorerIdaHooks, self).__init__()
super().__init__()
self.screen_ea_changed_hook = screen_ea_changed_hook
self.process_action_hooks = action_hooks

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
@@ -6,14 +6,15 @@
# 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 codecs
from typing import List, Iterator, Optional
import idc
import idaapi
from PyQt5 import QtCore
import capa.ida.helpers
from capa.features.address import Address, FileOffsetAddress, AbsoluteVirtualAddress
def info_to_name(display):
@@ -27,28 +28,27 @@ def info_to_name(display):
return ""
def location_to_hex(location):
"""convert location to hex for display"""
return "%08X" % location
def ea_to_hex(ea):
"""convert effective address (ea) to hex for display"""
return "%08X" % ea
class CapaExplorerDataItem(object):
class CapaExplorerDataItem:
"""store data for CapaExplorerDataModel"""
def __init__(self, parent, data):
def __init__(self, parent: Optional["CapaExplorerDataItem"], data: List[str], can_check=True):
"""initialize item"""
self.pred = parent
self._data = data
self.children = []
self._children: List["CapaExplorerDataItem"] = []
self._checked = False
self._can_check = can_check
# default state for item
self.flags = (
QtCore.Qt.ItemIsEnabled
| QtCore.Qt.ItemIsSelectable
| QtCore.Qt.ItemIsTristate
| QtCore.Qt.ItemIsUserCheckable
)
self.flags = QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
if self._can_check:
self.flags = self.flags | QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsTristate
if self.pred:
self.pred.appendChild(self)
@@ -70,33 +70,37 @@ class CapaExplorerDataItem(object):
"""
self._checked = checked
def canCheck(self):
""" """
return self._can_check
def isChecked(self):
"""get item is checked"""
return self._checked
def appendChild(self, item):
def appendChild(self, item: "CapaExplorerDataItem"):
"""add a new child to specified item
@param item: CapaExplorerDataItem
"""
self.children.append(item)
self._children.append(item)
def child(self, row):
def child(self, row: int) -> "CapaExplorerDataItem":
"""get child row
@param row: row number
"""
return self.children[row]
return self._children[row]
def childCount(self):
def childCount(self) -> int:
"""get child count"""
return len(self.children)
return len(self._children)
def columnCount(self):
def columnCount(self) -> int:
"""get column count"""
return len(self._data)
def data(self, column):
def data(self, column: int) -> Optional[str]:
"""get data at column
@param: column number
@@ -106,17 +110,17 @@ class CapaExplorerDataItem(object):
except IndexError:
return None
def parent(self):
def parent(self) -> Optional["CapaExplorerDataItem"]:
"""get parent"""
return self.pred
def row(self):
def row(self) -> int:
"""get row location"""
if self.pred:
return self.pred.children.index(self)
return self.pred._children.index(self)
return 0
def setData(self, column, value):
def setData(self, column: int, value: str):
"""set data in column
@param column: column number
@@ -124,14 +128,14 @@ class CapaExplorerDataItem(object):
"""
self._data[column] = value
def children(self):
def children(self) -> Iterator["CapaExplorerDataItem"]:
"""yield children"""
for child in self.children:
for child in self._children:
yield child
def removeChildren(self):
"""remove children"""
del self.children[:]
del self._children[:]
def __str__(self):
"""get string representation of columns
@@ -146,7 +150,7 @@ class CapaExplorerDataItem(object):
return self._data[0]
@property
def location(self):
def location(self) -> Optional[int]:
"""return data stored in location column"""
try:
# address stored as str, convert to int before return
@@ -165,7 +169,9 @@ class CapaExplorerRuleItem(CapaExplorerDataItem):
fmt = "%s (%d matches)"
def __init__(self, parent, name, namespace, count, source):
def __init__(
self, parent: CapaExplorerDataItem, name: str, namespace: str, count: int, source: str, can_check=True
):
"""initialize item
@param parent: parent node
@@ -175,7 +181,7 @@ class CapaExplorerRuleItem(CapaExplorerDataItem):
@param source: rule source (tooltip)
"""
display = self.fmt % (name, count) if count > 1 else name
super(CapaExplorerRuleItem, self).__init__(parent, [display, "", namespace])
super().__init__(parent, [display, "", namespace], can_check)
self._source = source
@property
@@ -187,19 +193,19 @@ class CapaExplorerRuleItem(CapaExplorerDataItem):
class CapaExplorerRuleMatchItem(CapaExplorerDataItem):
"""store data for rule match"""
def __init__(self, parent, display, source=""):
def __init__(self, parent: CapaExplorerDataItem, display: str, source=""):
"""initialize item
@param parent: parent node
@param display: text to display in UI
@param source: rule match source to display (tooltip)
"""
super(CapaExplorerRuleMatchItem, self).__init__(parent, [display, "", ""])
super().__init__(parent, [display, "", ""])
self._source = source
@property
def source(self):
""" return rule contents for display """
"""return rule contents for display"""
return self._source
@@ -208,20 +214,20 @@ class CapaExplorerFunctionItem(CapaExplorerDataItem):
fmt = "function(%s)"
def __init__(self, parent, location):
def __init__(self, parent: CapaExplorerDataItem, location: Address, can_check=True):
"""initialize item
@param parent: parent node
@param location: virtual address of function as seen by IDA
"""
super(CapaExplorerFunctionItem, self).__init__(
parent, [self.fmt % idaapi.get_name(location), location_to_hex(location), ""]
)
assert isinstance(location, AbsoluteVirtualAddress)
ea = int(location)
super().__init__(parent, [self.fmt % idaapi.get_name(ea), ea_to_hex(ea), ""], can_check)
@property
def info(self):
"""return function name"""
info = super(CapaExplorerFunctionItem, self).info
info = super().info
display = info_to_name(info)
return display if display else info
@@ -241,13 +247,13 @@ class CapaExplorerSubscopeItem(CapaExplorerDataItem):
fmt = "subscope(%s)"
def __init__(self, parent, scope):
def __init__(self, parent: CapaExplorerDataItem, scope):
"""initialize item
@param parent: parent node
@param scope: subscope name
"""
super(CapaExplorerSubscopeItem, self).__init__(parent, [self.fmt % scope, "", ""])
super().__init__(parent, [self.fmt % scope, "", ""])
class CapaExplorerBlockItem(CapaExplorerDataItem):
@@ -255,19 +261,29 @@ class CapaExplorerBlockItem(CapaExplorerDataItem):
fmt = "basic block(loc_%08X)"
def __init__(self, parent, location):
def __init__(self, parent: CapaExplorerDataItem, location: Address):
"""initialize item
@param parent: parent node
@param location: virtual address of basic block as seen by IDA
"""
super(CapaExplorerBlockItem, self).__init__(parent, [self.fmt % location, location_to_hex(location), ""])
assert isinstance(location, AbsoluteVirtualAddress)
ea = int(location)
super().__init__(parent, [self.fmt % ea, ea_to_hex(ea), ""])
class CapaExplorerInstructionItem(CapaExplorerBlockItem):
"""store data for instruction match"""
fmt = "instruction(loc_%08X)"
class CapaExplorerDefaultItem(CapaExplorerDataItem):
"""store data for default match e.g. statement (and, or)"""
def __init__(self, parent, display, details="", location=None):
def __init__(
self, parent: CapaExplorerDataItem, display: str, details: str = "", location: Optional[Address] = None
):
"""initialize item
@param parent: parent node
@@ -275,14 +291,20 @@ class CapaExplorerDefaultItem(CapaExplorerDataItem):
@param details: text to display in details section of UI
@param location: virtual address as seen by IDA
"""
location = location_to_hex(location) if location else ""
super(CapaExplorerDefaultItem, self).__init__(parent, [display, location, details])
ea = None
if location:
assert isinstance(location, AbsoluteVirtualAddress)
ea = int(location)
super().__init__(parent, [display, ea_to_hex(ea) if ea is not None else "", details])
class CapaExplorerFeatureItem(CapaExplorerDataItem):
"""store data for feature match"""
def __init__(self, parent, display, location="", details=""):
def __init__(
self, parent: CapaExplorerDataItem, display: str, location: Optional[Address] = None, details: str = ""
):
"""initialize item
@param parent: parent node
@@ -290,14 +312,18 @@ class CapaExplorerFeatureItem(CapaExplorerDataItem):
@param details: text to display in details section of UI
@param location: virtual address as seen by IDA
"""
location = location_to_hex(location) if location else ""
super(CapaExplorerFeatureItem, self).__init__(parent, [display, location, details])
if location:
assert isinstance(location, (AbsoluteVirtualAddress, FileOffsetAddress))
ea = int(location)
super().__init__(parent, [display, ea_to_hex(ea), details])
else:
super().__init__(parent, [display, "", details])
class CapaExplorerInstructionViewItem(CapaExplorerFeatureItem):
"""store data for instruction match"""
def __init__(self, parent, display, location):
def __init__(self, parent: CapaExplorerDataItem, display: str, location: Address):
"""initialize item
details section shows disassembly view for match
@@ -306,15 +332,17 @@ class CapaExplorerInstructionViewItem(CapaExplorerFeatureItem):
@param display: text to display in UI
@param location: virtual address as seen by IDA
"""
details = capa.ida.helpers.get_disasm_line(location)
super(CapaExplorerInstructionViewItem, self).__init__(parent, display, location=location, details=details)
self.ida_highlight = idc.get_color(location, idc.CIC_ITEM)
assert isinstance(location, AbsoluteVirtualAddress)
ea = int(location)
details = capa.ida.helpers.get_disasm_line(ea)
super().__init__(parent, display, location=location, details=details)
self.ida_highlight = idc.get_color(ea, idc.CIC_ITEM)
class CapaExplorerByteViewItem(CapaExplorerFeatureItem):
"""store data for byte match"""
def __init__(self, parent, display, location):
def __init__(self, parent: CapaExplorerDataItem, display: str, location: Address):
"""initialize item
details section shows byte preview for match
@@ -323,30 +351,32 @@ class CapaExplorerByteViewItem(CapaExplorerFeatureItem):
@param display: text to display in UI
@param location: virtual address as seen by IDA
"""
byte_snap = idaapi.get_bytes(location, 32)
assert isinstance(location, (AbsoluteVirtualAddress, FileOffsetAddress))
ea = int(location)
byte_snap = idaapi.get_bytes(ea, 32)
details = ""
if byte_snap:
byte_snap = codecs.encode(byte_snap, "hex").upper()
if sys.version_info >= (3, 0):
details = " ".join([byte_snap[i : i + 2].decode() for i in range(0, len(byte_snap), 2)])
else:
details = " ".join([byte_snap[i : i + 2] for i in range(0, len(byte_snap), 2)])
else:
details = ""
details = " ".join([byte_snap[i : i + 2].decode() for i in range(0, len(byte_snap), 2)])
super(CapaExplorerByteViewItem, self).__init__(parent, display, location=location, details=details)
self.ida_highlight = idc.get_color(location, idc.CIC_ITEM)
super().__init__(parent, display, location=location, details=details)
self.ida_highlight = idc.get_color(ea, idc.CIC_ITEM)
class CapaExplorerStringViewItem(CapaExplorerFeatureItem):
"""store data for string match"""
def __init__(self, parent, display, location, value):
def __init__(self, parent: CapaExplorerDataItem, display: str, location: Address, value: str):
"""initialize item
@param parent: parent node
@param display: text to display in UI
@param location: virtual address as seen by IDA
"""
super(CapaExplorerStringViewItem, self).__init__(parent, display, location=location, details=value)
self.ida_highlight = idc.get_color(location, idc.CIC_ITEM)
assert isinstance(location, (AbsoluteVirtualAddress, FileOffsetAddress))
ea = int(location)
super().__init__(parent, display, location=location, details=value)
self.ida_highlight = idc.get_color(ea, idc.CIC_ITEM)

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
@@ -6,14 +6,20 @@
# 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 Set, Dict, List, Tuple, Optional
from collections import deque
import idc
import idaapi
from PyQt5 import QtGui, QtCore
import capa.rules
import capa.ida.helpers
import capa.render.utils as rutils
import capa.features.common
import capa.features.freeze as frz
import capa.render.result_document as rd
import capa.features.freeze.features as frzf
from capa.ida.plugin.item import (
CapaExplorerDataItem,
CapaExplorerRuleItem,
@@ -25,8 +31,10 @@ from capa.ida.plugin.item import (
CapaExplorerSubscopeItem,
CapaExplorerRuleMatchItem,
CapaExplorerStringViewItem,
CapaExplorerInstructionItem,
CapaExplorerInstructionViewItem,
)
from capa.features.address import Address, AbsoluteVirtualAddress
# default highlight color used in IDA window
DEFAULT_HIGHLIGHT = 0xE6C700
@@ -43,7 +51,7 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel):
def __init__(self, parent=None):
"""initialize model"""
super(CapaExplorerDataModel, self).__init__(parent)
super().__init__(parent)
# root node does not have parent, contains header columns
self.root_node = CapaExplorerDataItem(None, ["Rule Information", "Address", "Details"])
@@ -110,6 +118,8 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel):
if role == QtCore.Qt.CheckStateRole and column == CapaExplorerDataModel.COLUMN_INDEX_RULE_INFORMATION:
# inform view how to display content of checkbox - un/checked
if not item.canCheck():
return None
return QtCore.Qt.Checked if item.isChecked() else QtCore.Qt.Unchecked
if role == QtCore.Qt.FontRole and column in (
@@ -133,6 +143,7 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel):
CapaExplorerFunctionItem,
CapaExplorerFeatureItem,
CapaExplorerSubscopeItem,
CapaExplorerInstructionItem,
),
)
and column == CapaExplorerDataModel.COLUMN_INDEX_RULE_INFORMATION
@@ -338,7 +349,14 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel):
return item.childCount()
def render_capa_doc_statement_node(self, parent, statement, locations, doc):
def render_capa_doc_statement_node(
self,
parent: CapaExplorerDataItem,
match: rd.Match,
statement: rd.Statement,
locations: List[Address],
doc: rd.ResultDocument,
):
"""render capa statement read from doc
@param parent: parent to which new child is assigned
@@ -346,85 +364,154 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel):
@param locations: locations of children (applies to range only?)
@param doc: result doc
"""
if statement["type"] in ("and", "or", "optional"):
display = statement["type"]
if statement.get("description"):
display += " (%s)" % statement["description"]
return CapaExplorerDefaultItem(parent, display)
elif statement["type"] == "not":
if isinstance(statement, rd.CompoundStatement):
if statement.type != rd.CompoundStatementType.NOT:
display = statement.type
if statement.description:
display += " (%s)" % statement.description
return CapaExplorerDefaultItem(parent, display)
elif isinstance(statement, rd.CompoundStatement) and statement.type == rd.CompoundStatementType.NOT:
# TODO: do we display 'not'
pass
elif statement["type"] == "some":
display = "%d or more" % statement["count"]
if statement.get("description"):
display += " (%s)" % statement["description"]
elif isinstance(statement, rd.SomeStatement):
display = "%d or more" % statement.count
if statement.description:
display += " (%s)" % statement.description
return CapaExplorerDefaultItem(parent, display)
elif statement["type"] == "range":
elif isinstance(statement, rd.RangeStatement):
# `range` is a weird node, its almost a hybrid of statement + feature.
# it is a specific feature repeated multiple times.
# there's no additional logic in the feature part, just the existence of a feature.
# so, we have to inline some of the feature rendering here.
display = "count(%s): " % self.capa_doc_feature_to_display(statement["child"])
display = "count(%s): " % self.capa_doc_feature_to_display(statement.child)
if statement["max"] == statement["min"]:
display += "%d" % (statement["min"])
elif statement["min"] == 0:
display += "%d or fewer" % (statement["max"])
elif statement["max"] == (1 << 64 - 1):
display += "%d or more" % (statement["min"])
if statement.max == statement.min:
display += "%d" % (statement.min)
elif statement.min == 0:
display += "%d or fewer" % (statement.max)
elif statement.max == (1 << 64 - 1):
display += "%d or more" % (statement.min)
else:
display += "between %d and %d" % (statement["min"], statement["max"])
display += "between %d and %d" % (statement.min, statement.max)
if statement.get("description"):
display += " (%s)" % statement["description"]
if statement.description:
display += " (%s)" % statement.description
parent2 = CapaExplorerFeatureItem(parent, display=display)
for location in locations:
# for each location render child node for range statement
self.render_capa_doc_feature(parent2, statement["child"], location, doc)
self.render_capa_doc_feature(parent2, match, statement.child, location, doc)
return parent2
elif statement["type"] == "subscope":
display = statement[statement["type"]]
if statement.get("description"):
display += " (%s)" % statement["description"]
elif isinstance(statement, rd.SubscopeStatement):
display = str(statement.scope)
if statement.description:
display += " (%s)" % statement.description
return CapaExplorerSubscopeItem(parent, display)
else:
raise RuntimeError("unexpected match statement type: " + str(statement))
def render_capa_doc_match(self, parent, match, doc):
def render_capa_doc_match(self, parent: CapaExplorerDataItem, match: rd.Match, doc: rd.ResultDocument):
"""render capa match read from doc
@param parent: parent node to which new child is assigned
@param match: match read from doc
@param doc: result doc
"""
if not match["success"]:
if not match.success:
# TODO: display failed branches at some point? Help with debugging rules?
return
# optional statement with no successful children is empty
if match["node"].get("statement", {}).get("type") == "optional" and not any(
map(lambda m: m["success"], match["children"])
):
return
if isinstance(match.node, rd.StatementNode) and match.node.statement.type == rd.CompoundStatementType.OPTIONAL:
if not any(map(lambda m: m.success, match.children)):
return
if match["node"]["type"] == "statement":
if isinstance(match.node, rd.StatementNode):
parent2 = self.render_capa_doc_statement_node(
parent, match["node"]["statement"], match.get("locations", []), doc
parent, match, match.node.statement, [addr.to_capa() for addr in match.locations], doc
)
elif match["node"]["type"] == "feature":
elif isinstance(match.node, rd.FeatureNode):
parent2 = self.render_capa_doc_feature_node(
parent, match["node"]["feature"], match.get("locations", []), doc
parent, match, match.node.feature, [addr.to_capa() for addr in match.locations], doc
)
else:
raise RuntimeError("unexpected node type: " + str(match["node"]["type"]))
raise RuntimeError("unexpected node type: " + str(match.node.type))
for child in match.get("children", []):
for child in match.children:
self.render_capa_doc_match(parent2, child, doc)
def render_capa_doc(self, doc):
def render_capa_doc_by_function(self, doc: rd.ResultDocument):
"""render rule matches by function meaning each rule match is nested under function where it was found"""
matches_by_function: Dict[AbsoluteVirtualAddress, Tuple[CapaExplorerFunctionItem, Set[str]]] = {}
for rule in rutils.capability_rules(doc):
match_eas: List[int] = []
# initial pass of rule matches
for addr_, _ in rule.matches:
addr: Address = addr_.to_capa()
if isinstance(addr, AbsoluteVirtualAddress):
match_eas.append(int(addr))
for ea in match_eas:
func_ea: Optional[int] = capa.ida.helpers.get_func_start_ea(ea)
if func_ea is None:
# rule match address is not located in a defined function
continue
func_address: AbsoluteVirtualAddress = AbsoluteVirtualAddress(func_ea)
if not matches_by_function.get(func_address, ()):
# create a new function root to nest its rule matches; Note: we must use the address of the
# function here so everything is displayed properly
matches_by_function[func_address] = (
CapaExplorerFunctionItem(self.root_node, func_address, can_check=False),
set(),
)
func_root, func_match_cache = matches_by_function[func_address]
if rule.meta.name in func_match_cache:
# only nest each rule once, so if found, skip
continue
# add matched rule to its function cache; create a new rule node whose parent is the matched
# function node
func_match_cache.add(rule.meta.name)
CapaExplorerRuleItem(
func_root,
rule.meta.name,
rule.meta.namespace or "",
len([ea for ea in match_eas if capa.ida.helpers.get_func_start_ea(ea) == func_ea]),
rule.source,
can_check=False,
)
def render_capa_doc_by_program(self, doc: rd.ResultDocument):
""" """
for rule in rutils.capability_rules(doc):
rule_name = rule.meta.name
rule_namespace = rule.meta.namespace or ""
parent = CapaExplorerRuleItem(self.root_node, rule_name, rule_namespace, len(rule.matches), rule.source)
for location_, match in rule.matches:
location = location_.to_capa()
parent2: CapaExplorerDataItem
if rule.meta.scope == capa.rules.FILE_SCOPE:
parent2 = parent
elif rule.meta.scope == capa.rules.FUNCTION_SCOPE:
parent2 = CapaExplorerFunctionItem(parent, location)
elif rule.meta.scope == capa.rules.BASIC_BLOCK_SCOPE:
parent2 = CapaExplorerBlockItem(parent, location)
elif rule.meta.scope == capa.rules.INSTRUCTION_SCOPE:
parent2 = CapaExplorerInstructionItem(parent, location)
else:
raise RuntimeError("unexpected rule scope: " + str(rule.meta.scope))
self.render_capa_doc_match(parent2, match, doc)
def render_capa_doc(self, doc: rd.ResultDocument, by_function: bool):
"""render capa features specified in doc
@param doc: capa result doc
@@ -432,45 +519,52 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel):
# inform model that changes are about to occur
self.beginResetModel()
for rule in rutils.capability_rules(doc):
rule_name = rule["meta"]["name"]
rule_namespace = rule["meta"].get("namespace")
parent = CapaExplorerRuleItem(
self.root_node, rule_name, rule_namespace, len(rule["matches"]), rule["source"]
)
for (location, match) in doc["rules"][rule["meta"]["name"]]["matches"].items():
if rule["meta"]["scope"] == capa.rules.FILE_SCOPE:
parent2 = parent
elif rule["meta"]["scope"] == capa.rules.FUNCTION_SCOPE:
parent2 = CapaExplorerFunctionItem(parent, location)
elif rule["meta"]["scope"] == capa.rules.BASIC_BLOCK_SCOPE:
parent2 = CapaExplorerBlockItem(parent, location)
else:
raise RuntimeError("unexpected rule scope: " + str(rule["meta"]["scope"]))
self.render_capa_doc_match(parent2, match, doc)
if by_function:
self.render_capa_doc_by_function(doc)
else:
self.render_capa_doc_by_program(doc)
# inform model changes have ended
self.endResetModel()
def capa_doc_feature_to_display(self, feature):
def capa_doc_feature_to_display(self, feature: frzf.Feature):
"""convert capa doc feature type string to display string for ui
@param feature: capa feature read from doc
"""
if feature[feature["type"]]:
if feature.get("description", ""):
return "%s(%s = %s)" % (feature["type"], feature[feature["type"]], feature["description"])
else:
return "%s(%s)" % (feature["type"], feature[feature["type"]])
else:
return "%s" % feature["type"]
key = feature.type
value = feature.dict(by_alias=True).get(feature.type)
def render_capa_doc_feature_node(self, parent, feature, locations, doc):
if value:
if isinstance(feature, frzf.StringFeature):
value = '"%s"' % capa.features.common.escape_string(value)
if isinstance(feature, frzf.PropertyFeature) and feature.access is not None:
key = f"property/{feature.access}"
elif isinstance(feature, frzf.OperandNumberFeature):
key = f"operand[{feature.index}].number"
elif isinstance(feature, frzf.OperandOffsetFeature):
key = f"operand[{feature.index}].offset"
if feature.description:
return "%s(%s = %s)" % (key, value, feature.description)
else:
return "%s(%s)" % (key, value)
else:
return "%s" % key
def render_capa_doc_feature_node(
self,
parent: CapaExplorerDataItem,
match: rd.Match,
feature: frzf.Feature,
locations: List[Address],
doc: rd.ResultDocument,
):
"""process capa doc feature node
@param parent: parent node to which child is assigned
@param match: match information
@param feature: capa doc feature node
@param locations: locations identified for feature
@param doc: capa doc
@@ -481,6 +575,7 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel):
# only one location for feature so no need to nest children
parent2 = self.render_capa_doc_feature(
parent,
match,
feature,
next(iter(locations)),
doc,
@@ -491,69 +586,114 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel):
parent2 = CapaExplorerFeatureItem(parent, display)
for location in sorted(locations):
self.render_capa_doc_feature(parent2, feature, location, doc)
self.render_capa_doc_feature(parent2, match, feature, location, doc)
return parent2
def render_capa_doc_feature(self, parent, feature, location, doc, display="-"):
def render_capa_doc_feature(
self,
parent: CapaExplorerDataItem,
match: rd.Match,
feature: frzf.Feature,
location: Address,
doc: rd.ResultDocument,
display="-",
):
"""render capa feature read from doc
@param parent: parent node to which new child is assigned
@param match: match information
@param feature: feature read from doc
@param doc: capa feature doc
@param location: address of feature
@param display: text to display in plugin UI
"""
# special handling for characteristic pending type
if feature["type"] == "characteristic":
if feature[feature["type"]] in ("embedded pe",):
if isinstance(feature, frzf.CharacteristicFeature):
characteristic = feature.characteristic
if characteristic in ("embedded pe",):
return CapaExplorerByteViewItem(parent, display, location)
if feature[feature["type"]] in ("loop", "recursive call", "tight loop"):
if characteristic in ("loop", "recursive call", "tight loop"):
return CapaExplorerFeatureItem(parent, display=display)
# default to instruction view for all other characteristics
return CapaExplorerInstructionViewItem(parent, display, location)
if feature["type"] == "match":
elif isinstance(feature, frzf.MatchFeature):
# display content of rule for all rule matches
return CapaExplorerRuleMatchItem(
parent, display, source=doc["rules"].get(feature[feature["type"]], {}).get("source", "")
)
matched_rule_source = ""
if feature["type"] == "regex":
return CapaExplorerStringViewItem(parent, display, location, feature["match"])
# check if match is a matched rule
matched_rule = doc.rules.get(feature.match, None)
if matched_rule is not None:
matched_rule_source = matched_rule.source
if feature["type"] == "basicblock":
return CapaExplorerRuleMatchItem(parent, display, source=matched_rule_source)
elif isinstance(feature, (frzf.RegexFeature, frzf.SubstringFeature)):
for capture, addrs in sorted(match.captures.items()):
for addr in addrs:
assert isinstance(addr, frz.Address)
if location == addr.value:
return CapaExplorerStringViewItem(
parent, display, location, '"' + capa.features.common.escape_string(capture) + '"'
)
# programming error: the given location should always be found in the regex matches
raise ValueError("regex match at location not found")
elif isinstance(feature, frzf.BasicBlockFeature):
return CapaExplorerBlockItem(parent, location)
if feature["type"] in (
"bytes",
"api",
"mnemonic",
"number",
"offset",
"number/x32",
"number/x64",
"offset/x32",
"offset/x64",
elif isinstance(
feature,
(
frzf.BytesFeature,
frzf.APIFeature,
frzf.MnemonicFeature,
frzf.NumberFeature,
frzf.OffsetFeature,
frzf.OperandNumberFeature,
frzf.OperandOffsetFeature,
),
):
# display instruction preview
return CapaExplorerInstructionViewItem(parent, display, location)
if feature["type"] in ("section",):
elif isinstance(feature, frzf.SectionFeature):
# display byte preview
return CapaExplorerByteViewItem(parent, display, location)
if feature["type"] in ("string",):
elif isinstance(feature, frzf.StringFeature):
# display string preview
return CapaExplorerStringViewItem(parent, display, location, feature[feature["type"]])
return CapaExplorerStringViewItem(
parent, display, location, '"%s"' % capa.features.common.escape_string(feature.string)
)
if feature["type"] in ("import", "export"):
elif isinstance(
feature,
(
frzf.ImportFeature,
frzf.ExportFeature,
frzf.FunctionNameFeature,
),
):
# display no preview
return CapaExplorerFeatureItem(parent, location=location, display=display)
elif isinstance(
feature,
(
frzf.ArchFeature,
frzf.OSFeature,
frzf.FormatFeature,
),
):
return CapaExplorerFeatureItem(parent, display=display)
raise RuntimeError("unexpected feature type: " + str(feature["type"]))
raise RuntimeError("unexpected feature type: " + str(feature.type))
def update_function_name(self, old_name, new_name):
"""update all instances of old function name with new function name

View File

@@ -1,11 +1,10 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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 six
from PyQt5 import QtCore
from PyQt5.QtCore import Qt
@@ -23,7 +22,7 @@ class CapaExplorerRangeProxyModel(QtCore.QSortFilterProxyModel):
def __init__(self, parent=None):
"""initialize proxy filter"""
super(CapaExplorerRangeProxyModel, self).__init__(parent)
super().__init__(parent)
self.min_ea = None
self.max_ea = None
@@ -93,7 +92,7 @@ class CapaExplorerRangeProxyModel(QtCore.QSortFilterProxyModel):
@param parent: QModelIndex of parent
"""
# filter not set
if self.min_ea is None and self.max_ea is None:
if self.min_ea is None or self.max_ea is None:
return True
index = self.sourceModel().index(row, 0, parent)
@@ -146,7 +145,7 @@ class CapaExplorerSearchProxyModel(QtCore.QSortFilterProxyModel):
def __init__(self, parent=None):
""" """
super(CapaExplorerSearchProxyModel, self).__init__(parent)
super().__init__(parent)
self.query = ""
self.setFilterKeyColumn(-1) # all columns
@@ -208,7 +207,7 @@ class CapaExplorerSearchProxyModel(QtCore.QSortFilterProxyModel):
if not data:
continue
if not isinstance(data, six.string_types):
if not isinstance(data, str):
# sanity check: should already be a string, but double check
continue

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

70
capa/optimizer.py Normal file
View File

@@ -0,0 +1,70 @@
import logging
import capa.engine as ceng
import capa.features.common
logger = logging.getLogger(__name__)
def get_node_cost(node):
if isinstance(node, (capa.features.common.OS, capa.features.common.Arch, capa.features.common.Format)):
# we assume these are the most restrictive features:
# authors commonly use them at the start of rules to restrict the category of samples to inspect
return 0
# elif "everything else":
# return 1
#
# this should be all hash-lookup features.
# see below.
elif isinstance(node, (capa.features.common.Substring, capa.features.common.Regex, capa.features.common.Bytes)):
# substring and regex features require a full scan of each string
# which we anticipate is more expensive then a hash lookup feature (e.g. mnemonic or count).
#
# TODO: compute the average cost of these feature relative to hash feature
# and adjust the factor accordingly.
return 2
elif isinstance(node, (ceng.Not, ceng.Range)):
# the cost of these nodes are defined by the complexity of their single child.
return 1 + get_node_cost(node.child)
elif isinstance(node, (ceng.And, ceng.Or, ceng.Some)):
# the cost of these nodes is the full cost of their children
# as this is the worst-case scenario.
return 1 + sum(map(get_node_cost, node.children))
else:
# this should be all hash-lookup features.
# we give this a arbitrary weight of 1.
# the only thing more "important" than this is checking OS/Arch/Format.
return 1
def optimize_statement(statement):
# this routine operates in-place
if isinstance(statement, (ceng.And, ceng.Or, ceng.Some)):
# has .children
statement.children = sorted(statement.children, key=get_node_cost)
return
elif isinstance(statement, (ceng.Not, ceng.Range)):
# has .child
optimize_statement(statement.child)
return
else:
# appears to be "simple"
return
def optimize_rule(rule):
# this routine operates in-place
optimize_statement(rule.statement)
def optimize_rules(rules):
logger.debug("optimizing %d rules", len(rules))
for rule in rules:
optimize_rule(rule)
return rules

10
capa/perf.py Normal file
View File

@@ -0,0 +1,10 @@
import typing
import collections
# this structure is unstable and may change before the next major release.
counters: typing.Counter[str] = collections.Counter()
def reset():
global counters
counters = collections.Counter()

View File

@@ -1,266 +0,0 @@
# Copyright (C) 2020 FireEye, 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 six
import capa.rules
import capa.engine
def convert_statement_to_result_document(statement):
"""
"statement": {
"type": "or"
},
"statement": {
"max": 9223372036854775808,
"min": 2,
"type": "range"
},
"""
statement_type = statement.name.lower()
result = {"type": statement_type}
if statement.description:
result["description"] = statement.description
if statement_type == "some" and statement.count == 0:
result["type"] = "optional"
elif statement_type == "some":
result["count"] = statement.count
elif statement_type == "range":
result["min"] = statement.min
result["max"] = statement.max
result["child"] = convert_feature_to_result_document(statement.child)
elif statement_type == "subscope":
result["subscope"] = statement.scope
return result
def convert_feature_to_result_document(feature):
"""
"feature": {
"number": 6,
"type": "number"
},
"feature": {
"api": "ws2_32.WSASocket",
"type": "api"
},
"feature": {
"match": "create TCP socket",
"type": "match"
},
"feature": {
"characteristic": [
"loop",
true
],
"type": "characteristic"
},
"""
result = {"type": feature.name, feature.name: feature.get_value_str()}
if feature.description:
result["description"] = feature.description
if feature.name == "regex":
result["match"] = feature.match
return result
def convert_node_to_result_document(node):
"""
"node": {
"type": "statement",
"statement": { ... }
},
"node": {
"type": "feature",
"feature": { ... }
},
"""
if isinstance(node, capa.engine.Statement):
return {
"type": "statement",
"statement": convert_statement_to_result_document(node),
}
elif isinstance(node, capa.features.Feature):
return {
"type": "feature",
"feature": convert_feature_to_result_document(node),
}
else:
raise RuntimeError("unexpected match node type")
def convert_match_to_result_document(rules, capabilities, result):
"""
convert the given Result instance into a common, Python-native data structure.
this will become part of the "result document" format that can be emitted to JSON.
"""
doc = {
"success": bool(result.success),
"node": convert_node_to_result_document(result.statement),
"children": [convert_match_to_result_document(rules, capabilities, child) for child in result.children],
}
# logic expression, like `and`, don't have locations - their children do.
# so only add `locations` to feature nodes.
if isinstance(result.statement, capa.features.Feature):
if bool(result.success):
doc["locations"] = result.locations
elif isinstance(result.statement, capa.rules.Range):
if bool(result.success):
doc["locations"] = result.locations
# if we have a `match` statement, then we're referencing another rule.
# this could an external rule (written by a human), or
# rule generated to support a subscope (basic block, etc.)
# we still want to include the matching logic in this tree.
#
# so, we need to lookup the other rule results
# and then filter those down to the address used here.
# finally, splice that logic into this tree.
if (
doc["node"]["type"] == "feature"
and doc["node"]["feature"]["type"] == "match"
# only add subtree on success,
# because there won't be results for the other rule on failure.
and doc["success"]
):
rule_name = doc["node"]["feature"]["match"]
rule = rules[rule_name]
rule_matches = {address: result for (address, result) in capabilities[rule_name]}
if rule.meta.get("capa/subscope-rule"):
# for a subscope rule, fixup the node to be a scope node, rather than a match feature node.
#
# e.g. `contain loop/30c4c78e29bf4d54894fc74f664c62e8` -> `basic block`
scope = rule.meta["scope"]
doc["node"] = {
"type": "statement",
"statement": {
"type": "subscope",
"subscope": scope,
},
}
for location in doc["locations"]:
doc["children"].append(convert_match_to_result_document(rules, capabilities, rule_matches[location]))
return doc
def convert_capabilities_to_result_document(meta, rules, capabilities):
"""
convert the given rule set and capabilities result to a common, Python-native data structure.
this format can be directly emitted to JSON, or passed to the other `render_*` routines
to render as text.
see examples of substructures in above routines.
schema:
```json
{
"meta": {...},
"rules: {
$rule-name: {
"meta": {...copied from rule.meta...},
"matches: {
$address: {...match details...},
...
}
},
...
}
}
```
Args:
meta (Dict[str, Any]):
rules (RuleSet):
capabilities (Dict[str, List[Tuple[int, Result]]]):
"""
doc = {
"meta": meta,
"rules": {},
}
for rule_name, matches in capabilities.items():
rule = rules[rule_name]
if rule.meta.get("capa/subscope-rule"):
continue
doc["rules"][rule_name] = {
"meta": dict(rule.meta),
"source": rule.definition,
"matches": {
addr: convert_match_to_result_document(rules, capabilities, match) for (addr, match) in matches
},
}
return doc
def render_vverbose(meta, rules, capabilities):
# there's an import loop here
# if capa.render imports capa.render.vverbose
# and capa.render.vverbose import capa.render (implicitly, as a submodule)
# so, defer the import until routine is called, breaking the import loop.
import capa.render.vverbose
doc = convert_capabilities_to_result_document(meta, rules, capabilities)
return capa.render.vverbose.render_vverbose(doc)
def render_verbose(meta, rules, capabilities):
# break import loop
import capa.render.verbose
doc = convert_capabilities_to_result_document(meta, rules, capabilities)
return capa.render.verbose.render_verbose(doc)
def render_default(meta, rules, capabilities):
# break import loop
import capa.render.default
import capa.render.verbose
doc = convert_capabilities_to_result_document(meta, rules, capabilities)
return capa.render.default.render_default(doc)
class CapaJsonObjectEncoder(json.JSONEncoder):
"""JSON encoder that emits Python sets as sorted lists"""
def default(self, obj):
if isinstance(obj, (list, dict, int, float, bool, type(None))) or isinstance(obj, six.string_types):
return json.JSONEncoder.default(self, obj)
elif isinstance(obj, set):
return list(sorted(obj))
else:
# probably will TypeError
return json.JSONEncoder.default(self, obj)
def render_json(meta, rules, capabilities):
return json.dumps(
convert_capabilities_to_result_document(meta, rules, capabilities),
cls=CapaJsonObjectEncoder,
sort_keys=True,
)

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
@@ -8,15 +8,20 @@
import collections
import six
import tabulate
import capa.render.utils as rutils
import capa.features.freeze as frz
import capa.render.result_document as rd
import capa.features.freeze.features as frzf
from capa.rules import RuleSet
from capa.engine import MatchResults
from capa.render.utils import StringIO
tabulate.PRESERVE_WHITESPACE = True
def width(s, character_count):
def width(s: str, character_count: int) -> str:
"""pad the given string to at least `character_count`"""
if len(s) < character_count:
return s + " " * (character_count - len(s))
@@ -24,47 +29,49 @@ def width(s, character_count):
return s
def render_meta(doc, ostream):
def render_meta(doc: rd.ResultDocument, ostream: StringIO):
rows = [
(width("md5", 22), width(doc["meta"]["sample"]["md5"], 82)),
("sha1", doc["meta"]["sample"]["sha1"]),
("sha256", doc["meta"]["sample"]["sha256"]),
("path", doc["meta"]["sample"]["path"]),
(width("md5", 22), width(doc.meta.sample.md5, 82)),
("sha1", doc.meta.sample.sha1),
("sha256", doc.meta.sample.sha256),
("os", doc.meta.analysis.os),
("format", doc.meta.analysis.format),
("arch", doc.meta.analysis.arch),
("path", doc.meta.sample.path),
]
ostream.write(tabulate.tabulate(rows, tablefmt="psql"))
ostream.write("\n")
def find_subrule_matches(doc):
def find_subrule_matches(doc: rd.ResultDocument):
"""
collect the rule names that have been matched as a subrule match.
this way we can avoid displaying entries for things that are too specific.
"""
matches = set([])
def rec(node):
if not node["success"]:
def rec(match: rd.Match):
if not match.success:
# there's probably a bug here for rules that do `not: match: ...`
# but we don't have any examples of this yet
return
elif node["node"]["type"] == "statement":
for child in node["children"]:
elif isinstance(match.node, rd.StatementNode):
for child in match.children:
rec(child)
elif node["node"]["type"] == "feature":
if node["node"]["feature"]["type"] == "match":
matches.add(node["node"]["feature"]["match"])
elif isinstance(match.node, rd.FeatureNode) and isinstance(match.node.feature, frzf.MatchFeature):
matches.add(match.node.feature.match)
for rule in rutils.capability_rules(doc):
for node in rule["matches"].values():
rec(node)
for address, match in rule.matches:
rec(match)
return matches
def render_capabilities(doc, ostream):
def render_capabilities(doc: rd.ResultDocument, ostream: StringIO):
"""
example::
@@ -80,18 +87,18 @@ def render_capabilities(doc, ostream):
rows = []
for rule in rutils.capability_rules(doc):
if rule["meta"]["name"] in subrule_matches:
if rule.meta.name in subrule_matches:
# rules that are also matched by other rules should not get rendered by default.
# this cuts down on the amount of output while giving approx the same detail.
# see #224
continue
count = len(rule["matches"])
count = len(rule.matches)
if count == 1:
capability = rutils.bold(rule["meta"]["name"])
capability = rutils.bold(rule.meta.name)
else:
capability = "%s (%d matches)" % (rutils.bold(rule["meta"]["name"]), count)
rows.append((capability, rule["meta"]["namespace"]))
capability = "%s (%d matches)" % (rutils.bold(rule.meta.name), count)
rows.append((capability, rule.meta.namespace))
if rows:
ostream.write(
@@ -102,7 +109,7 @@ def render_capabilities(doc, ostream):
ostream.writeln(rutils.bold("no capabilities found"))
def render_attack(doc, ostream):
def render_attack(doc: rd.ResultDocument, ostream: StringIO):
"""
example::
@@ -120,31 +127,17 @@ def render_attack(doc, ostream):
"""
tactics = collections.defaultdict(set)
for rule in rutils.capability_rules(doc):
if not rule["meta"].get("att&ck"):
continue
for attack in rule["meta"]["att&ck"]:
tactic, _, rest = attack.partition("::")
if "::" in rest:
technique, _, rest = rest.partition("::")
subtechnique, _, id = rest.rpartition(" ")
tactics[tactic].add((technique, subtechnique, id))
else:
technique, _, id = rest.rpartition(" ")
tactics[tactic].add((technique, id))
for attack in rule.meta.attack:
tactics[attack.tactic].add((attack.technique, attack.subtechnique, attack.id))
rows = []
for tactic, techniques in sorted(tactics.items()):
inner_rows = []
for spec in sorted(techniques):
if len(spec) == 2:
technique, id = spec
for technique, subtechnique, id in sorted(techniques):
if not subtechnique:
inner_rows.append("%s %s" % (rutils.bold(technique), id))
elif len(spec) == 3:
technique, subtechnique, id = spec
inner_rows.append("%s::%s %s" % (rutils.bold(technique), subtechnique, id))
else:
raise RuntimeError("unexpected ATT&CK spec format")
inner_rows.append("%s::%s %s" % (rutils.bold(technique), subtechnique, id))
rows.append(
(
rutils.bold(tactic.upper()),
@@ -161,13 +154,61 @@ def render_attack(doc, ostream):
ostream.write("\n")
def render_default(doc):
def render_mbc(doc: rd.ResultDocument, ostream: StringIO):
"""
example::
+--------------------------+------------------------------------------------------------+
| MBC Objective | MBC Behavior |
|--------------------------+------------------------------------------------------------|
| ANTI-BEHAVIORAL ANALYSIS | Virtual Machine Detection::Instruction Testing [B0009.029] |
| COLLECTION | Keylogging::Polling [F0002.002] |
| COMMUNICATION | Interprocess Communication::Create Pipe [C0003.001] |
| | Interprocess Communication::Write Pipe [C0003.004] |
| IMPACT | Remote Access::Reverse Shell [B0022.001] |
+--------------------------+------------------------------------------------------------+
"""
objectives = collections.defaultdict(set)
for rule in rutils.capability_rules(doc):
for mbc in rule.meta.mbc:
objectives[mbc.objective].add((mbc.behavior, mbc.method, mbc.id))
rows = []
for objective, behaviors in sorted(objectives.items()):
inner_rows = []
for behavior, method, id in sorted(behaviors):
if not method:
inner_rows.append("%s [%s]" % (rutils.bold(behavior), id))
else:
inner_rows.append("%s::%s [%s]" % (rutils.bold(behavior), method, id))
rows.append(
(
rutils.bold(objective.upper()),
"\n".join(inner_rows),
)
)
if rows:
ostream.write(
tabulate.tabulate(rows, headers=[width("MBC Objective", 25), width("MBC Behavior", 75)], tablefmt="psql")
)
ostream.write("\n")
def render_default(doc: rd.ResultDocument):
ostream = rutils.StringIO()
render_meta(doc, ostream)
ostream.write("\n")
render_attack(doc, ostream)
ostream.write("\n")
render_mbc(doc, ostream)
ostream.write("\n")
render_capabilities(doc, ostream)
return ostream.getvalue()
def render(meta, rules: RuleSet, capabilities: MatchResults) -> str:
doc = rd.ResultDocument.from_capa(meta, rules, capabilities)
return render_default(doc)

14
capa/render/json.py Normal file
View File

@@ -0,0 +1,14 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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 capa.render.result_document as rd
from capa.rules import RuleSet
from capa.engine import MatchResults
def render(meta, rules: RuleSet, capabilities: MatchResults) -> str:
return rd.ResultDocument.from_capa(meta, rules, capabilities).json(exclude_none=True)

View File

@@ -0,0 +1,542 @@
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
# 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 datetime
from typing import Any, Dict, Tuple, Union, Optional
from pydantic import Field, BaseModel
import capa.rules
import capa.engine
import capa.features.common
import capa.features.freeze as frz
import capa.features.address
import capa.features.freeze.features as frzf
from capa.rules import RuleSet
from capa.engine import MatchResults
from capa.helpers import assert_never
class FrozenModel(BaseModel):
class Config:
frozen = True
class Sample(FrozenModel):
md5: str
sha1: str
sha256: str
path: str
class BasicBlockLayout(FrozenModel):
address: frz.Address
class FunctionLayout(FrozenModel):
address: frz.Address
matched_basic_blocks: Tuple[BasicBlockLayout, ...]
class Layout(FrozenModel):
functions: Tuple[FunctionLayout, ...]
class LibraryFunction(FrozenModel):
address: frz.Address
name: str
class FunctionFeatureCount(FrozenModel):
address: frz.Address
count: int
class FeatureCounts(FrozenModel):
file: int
functions: Tuple[FunctionFeatureCount, ...]
class Analysis(FrozenModel):
format: str
arch: str
os: str
extractor: str
rules: Tuple[str, ...]
base_address: frz.Address
layout: Layout
feature_counts: FeatureCounts
library_functions: Tuple[LibraryFunction, ...]
class Metadata(FrozenModel):
timestamp: datetime.datetime
version: str
argv: Optional[Tuple[str, ...]]
sample: Sample
analysis: Analysis
@classmethod
def from_capa(cls, meta: Any) -> "Metadata":
return cls(
timestamp=meta["timestamp"],
version=meta["version"],
argv=meta["argv"] if "argv" in meta else None,
sample=Sample(
md5=meta["sample"]["md5"],
sha1=meta["sample"]["sha1"],
sha256=meta["sample"]["sha256"],
path=meta["sample"]["path"],
),
analysis=Analysis(
format=meta["analysis"]["format"],
arch=meta["analysis"]["arch"],
os=meta["analysis"]["os"],
extractor=meta["analysis"]["extractor"],
rules=meta["analysis"]["rules"],
base_address=frz.Address.from_capa(meta["analysis"]["base_address"]),
layout=Layout(
functions=tuple(
FunctionLayout(
address=frz.Address.from_capa(address),
matched_basic_blocks=tuple(
BasicBlockLayout(address=frz.Address.from_capa(bb)) for bb in f["matched_basic_blocks"]
),
)
for address, f in meta["analysis"]["layout"]["functions"].items()
)
),
feature_counts=FeatureCounts(
file=meta["analysis"]["feature_counts"]["file"],
functions=tuple(
FunctionFeatureCount(address=frz.Address.from_capa(address), count=count)
for address, count in meta["analysis"]["feature_counts"]["functions"].items()
),
),
library_functions=tuple(
LibraryFunction(address=frz.Address.from_capa(address), name=name)
for address, name in meta["analysis"]["library_functions"].items()
),
),
)
class CompoundStatementType:
AND = "and"
OR = "or"
NOT = "not"
OPTIONAL = "optional"
class StatementModel(FrozenModel):
...
class CompoundStatement(StatementModel):
type: str
description: Optional[str] = None
class SomeStatement(StatementModel):
type = "some"
description: Optional[str] = None
count: int
class RangeStatement(StatementModel):
type = "range"
description: Optional[str] = None
min: int
max: int
child: frz.Feature
class SubscopeStatement(StatementModel):
type = "subscope"
description: Optional[str] = None
scope: capa.rules.Scope
Statement = Union[
# Note! order matters, see #1161
RangeStatement,
SomeStatement,
SubscopeStatement,
CompoundStatement,
]
class StatementNode(FrozenModel):
type = "statement"
statement: Statement
def statement_from_capa(node: capa.engine.Statement) -> Statement:
if isinstance(node, (capa.engine.And, capa.engine.Or, capa.engine.Not)):
return CompoundStatement(type=node.__class__.__name__.lower(), description=node.description)
elif isinstance(node, capa.engine.Some):
if node.count == 0:
return CompoundStatement(type=CompoundStatementType.OPTIONAL, description=node.description)
else:
return SomeStatement(
description=node.description,
count=node.count,
)
elif isinstance(node, capa.engine.Range):
return RangeStatement(
description=node.description,
min=node.min,
max=node.max,
child=frz.feature_from_capa(node.child),
)
elif isinstance(node, capa.engine.Subscope):
return SubscopeStatement(
description=node.description,
scope=capa.rules.Scope(node.scope),
)
else:
raise NotImplementedError(f"statement_from_capa({type(node)}) not implemented")
class FeatureNode(FrozenModel):
type = "feature"
feature: frz.Feature
Node = Union[StatementNode, FeatureNode]
def node_from_capa(node: Union[capa.engine.Statement, capa.engine.Feature]) -> Node:
if isinstance(node, capa.engine.Statement):
return StatementNode(statement=statement_from_capa(node))
elif isinstance(node, capa.engine.Feature):
return FeatureNode(feature=frz.feature_from_capa(node))
else:
assert_never(node)
class Match(BaseModel):
"""
args:
success: did the node match?
node: the logic node or feature node.
children: any children of the logic node. not relevent for features, can be empty.
locations: where the feature matched. not relevant for logic nodes (except range), can be empty.
captures: captured values from the string/regex feature, and the locations of those values.
"""
success: bool
node: Node
children: Tuple["Match", ...]
locations: Tuple[frz.Address, ...]
captures: Dict[str, Tuple[frz.Address, ...]]
@classmethod
def from_capa(
cls,
rules: RuleSet,
capabilities: MatchResults,
result: capa.engine.Result,
) -> "Match":
success = bool(result)
node = node_from_capa(result.statement)
children = [Match.from_capa(rules, capabilities, child) for child in result.children]
# logic expression, like `and`, don't have locations - their children do.
# so only add `locations` to feature nodes.
locations = []
if isinstance(node, FeatureNode) and success:
locations = list(map(frz.Address.from_capa, result.locations))
elif isinstance(node, StatementNode) and isinstance(node.statement, RangeStatement) and success:
locations = list(map(frz.Address.from_capa, result.locations))
captures = {}
if isinstance(result.statement, (capa.features.common._MatchedSubstring, capa.features.common._MatchedRegex)):
captures = {
capture: list(map(frz.Address.from_capa, locs)) for capture, locs in result.statement.matches.items()
}
# if we have a `match` statement, then we're referencing another rule or namespace.
# this could an external rule (written by a human), or
# rule generated to support a subscope (basic block, etc.)
# we still want to include the matching logic in this tree.
#
# so, we need to lookup the other rule results
# and then filter those down to the address used here.
# finally, splice that logic into this tree.
if (
isinstance(node, FeatureNode)
and isinstance(node.feature, frzf.MatchFeature)
# only add subtree on success,
# because there won't be results for the other rule on failure.
and success
):
name = node.feature.match
if name in rules:
# this is a rule that we're matching
#
# pull matches from the referenced rule into our tree here.
rule_name = name
rule = rules[rule_name]
rule_matches = {address: result for (address, result) in capabilities[rule_name]}
if rule.is_subscope_rule():
# for a subscope rule, fixup the node to be a scope node, rather than a match feature node.
#
# e.g. `contain loop/30c4c78e29bf4d54894fc74f664c62e8` -> `basic block`
#
# note! replace `node`
node = StatementNode(
statement=SubscopeStatement(
scope=rule.meta["scope"],
)
)
for location in result.locations:
children.append(Match.from_capa(rules, capabilities, rule_matches[location]))
else:
# this is a namespace that we're matching
#
# check for all rules in the namespace,
# seeing if they matched.
# if so, pull their matches into our match tree here.
ns_name = name
ns_rules = rules.rules_by_namespace[ns_name]
for rule in ns_rules:
if rule.name in capabilities:
# the rule matched, so splice results into our tree here.
#
# note, there's a shortcoming in our result document schema here:
# we lose the name of the rule that matched in a namespace.
# for example, if we have a statement: `match: runtime/dotnet`
# and we get matches, we can say the following:
#
# match: runtime/dotnet @ 0x0
# or:
# import: mscoree._CorExeMain @ 0x402000
#
# however, we lose the fact that it was rule
# "compiled to the .NET platform"
# that contained this logic and did the match.
#
# we could introduce an intermediate node here.
# this would be a breaking change and require updates to the renderers.
# in the meantime, the above might be sufficient.
rule_matches = {address: result for (address, result) in capabilities[rule.name]}
for location in result.locations:
# doc[locations] contains all matches for the given namespace.
# for example, the feature might be `match: anti-analysis/packer`
# which matches against "generic unpacker" and "UPX".
# in this case, doc[locations] contains locations for *both* of thse.
#
# rule_matches contains the matches for the specific rule.
# this is a subset of doc[locations].
#
# so, grab only the locations for current rule.
if location in rule_matches:
children.append(Match.from_capa(rules, capabilities, rule_matches[location]))
return cls(
success=success,
node=node,
children=children,
locations=locations,
captures=captures,
)
def parse_parts_id(s: str):
id_ = ""
parts = s.split("::")
if len(parts) > 0:
last = parts.pop()
last, _, id_ = last.rpartition(" ")
id_ = id_.lstrip("[").rstrip("]")
parts.append(last)
return tuple(parts), id_
class AttackSpec(FrozenModel):
"""
given an ATT&CK spec like: `Tactic::Technique::Subtechnique [Identifier]`
e.g., `Execution::Command and Scripting Interpreter::Python [T1059.006]`
args:
tactic: like `Tactic` above, perhaps "Execution"
technique: like `Technique` above, perhaps "Command and Scripting Interpreter"
subtechnique: like `Subtechnique` above, perhaps "Python"
id: like `Identifier` above, perhaps "T1059.006"
"""
parts: Tuple[str, ...]
tactic: str
technique: str
subtechnique: str
id: str
@classmethod
def from_str(cls, s) -> "AttackSpec":
tactic = ""
technique = ""
subtechnique = ""
parts, id_ = parse_parts_id(s)
if len(parts) > 0:
tactic = parts[0]
if len(parts) > 1:
technique = parts[1]
if len(parts) > 2:
subtechnique = parts[2]
return cls(
parts=parts,
tactic=tactic,
technique=technique,
subtechnique=subtechnique,
id=id_,
)
class MBCSpec(FrozenModel):
"""
given an MBC spec like: `Objective::Behavior::Method [Identifier]`
e.g., `Collection::Input Capture::Mouse Events [E1056.m01]`
args:
objective: like `Objective` above, perhaps "Collection"
behavior: like `Behavior` above, perhaps "Input Capture"
method: like `Method` above, perhaps "Mouse Events"
id: like `Identifier` above, perhaps "E1056.m01"
"""
parts: Tuple[str, ...]
objective: str
behavior: str
method: str
id: str
@classmethod
def from_str(cls, s) -> "MBCSpec":
objective = ""
behavior = ""
method = ""
parts, id_ = parse_parts_id(s)
if len(parts) > 0:
objective = parts[0]
if len(parts) > 1:
behavior = parts[1]
if len(parts) > 2:
method = parts[2]
return cls(
parts=parts,
objective=objective,
behavior=behavior,
method=method,
id=id_,
)
class MaecMetadata(FrozenModel):
analysis_conclusion: Optional[str] = Field(None, alias="analysis-conclusion")
analysis_conclusion_ov: Optional[str] = Field(None, alias="analysis-conclusion-ov")
malware_family: Optional[str] = Field(None, alias="malware-family")
malware_category: Optional[str] = Field(None, alias="malware-category")
malware_category_ov: Optional[str] = Field(None, alias="malware-category-ov")
class Config:
frozen = True
allow_population_by_field_name = True
class RuleMetadata(FrozenModel):
name: str
namespace: Optional[str]
authors: Tuple[str, ...]
scope: capa.rules.Scope
attack: Tuple[AttackSpec, ...] = Field(alias="att&ck")
mbc: Tuple[MBCSpec, ...]
references: Tuple[str, ...]
examples: Tuple[str, ...]
description: str
lib: bool = Field(False, alias="lib")
is_subscope_rule: bool = Field(False, alias="capa/subscope")
maec: MaecMetadata
@classmethod
def from_capa(cls, rule: capa.rules.Rule) -> "RuleMetadata":
return cls(
name=rule.meta.get("name"),
namespace=rule.meta.get("namespace"),
authors=rule.meta.get("authors"),
scope=capa.rules.Scope(rule.meta.get("scope")),
attack=list(map(AttackSpec.from_str, rule.meta.get("att&ck", []))),
mbc=list(map(MBCSpec.from_str, rule.meta.get("mbc", []))),
references=rule.meta.get("references", []),
examples=rule.meta.get("examples", []),
description=rule.meta.get("description", ""),
lib=rule.meta.get("lib", False),
capa_subscope=rule.meta.get("capa/subscope", False),
maec=MaecMetadata(
analysis_conclusion=rule.meta.get("maec/analysis-conclusion"),
analysis_conclusion_ov=rule.meta.get("maec/analysis-conclusion-ov"),
malware_family=rule.meta.get("maec/malware-family"),
malware_category=rule.meta.get("maec/malware-category"),
malware_category_ov=rule.meta.get("maec/malware-category-ov"),
),
)
class Config:
frozen = True
allow_population_by_field_name = True
class RuleMatches(BaseModel):
"""
args:
meta: the metadata from the rule
source: the raw rule text
"""
meta: RuleMetadata
source: str
matches: Tuple[Tuple[frz.Address, Match], ...]
class ResultDocument(BaseModel):
meta: Metadata
rules: Dict[str, RuleMatches]
@classmethod
def from_capa(cls, meta, rules: RuleSet, capabilities: MatchResults) -> "ResultDocument":
rule_matches: Dict[str, RuleMatches] = {}
for rule_name, matches in capabilities.items():
rule = rules[rule_name]
if rule.meta.get("capa/subscope-rule"):
continue
rule_matches[rule_name] = RuleMatches(
meta=RuleMetadata.from_capa(rule),
source=rule.definition,
matches=tuple(
(frz.Address.from_capa(addr), Match.from_capa(rules, capabilities, match))
for addr, match in matches
),
)
return ResultDocument(meta=Metadata.from_capa(meta), rules=rule_matches)

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
@@ -6,50 +6,57 @@
# 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 six
import io
from typing import Union, Iterator
import termcolor
import capa.render.result_document as rd
def bold(s):
def bold(s: str) -> str:
"""draw attention to the given string"""
return termcolor.colored(s, "blue")
def bold2(s):
def bold2(s: str) -> str:
"""draw attention to the given string, within a `bold` section"""
return termcolor.colored(s, "green")
def hex(n):
"""render the given number using upper case hex, like: 0x123ABC"""
if n < 0:
return "-0x%X" % (-n)
else:
return "0x%X" % n
def warn(s: str) -> str:
return termcolor.colored(s, "yellow")
def capability_rules(doc):
def format_parts_id(data: Union[rd.AttackSpec, rd.MBCSpec]):
"""
format canonical representation of ATT&CK/MBC parts and ID
"""
return "%s [%s]" % ("::".join(data.parts), data.id)
def capability_rules(doc: rd.ResultDocument) -> Iterator[rd.RuleMatches]:
"""enumerate the rules in (namespace, name) order that are 'capability' rules (not lib/subscope/disposition/etc)."""
for (_, _, rule) in sorted(
map(lambda rule: (rule["meta"].get("namespace", ""), rule["meta"]["name"], rule), doc["rules"].values())
):
if rule["meta"].get("lib"):
for _, _, rule in sorted(map(lambda rule: (rule.meta.namespace or "", rule.meta.name, rule), doc.rules.values())):
if rule.meta.lib:
continue
if rule["meta"].get("capa/subscope"):
if rule.meta.is_subscope_rule:
continue
if rule["meta"].get("maec/analysis-conclusion"):
if rule.meta.maec.analysis_conclusion:
continue
if rule["meta"].get("maec/analysis-conclusion-ov"):
if rule.meta.maec.analysis_conclusion_ov:
continue
if rule["meta"].get("maec/malware-category"):
if rule.meta.maec.malware_family:
continue
if rule["meta"].get("maec/malware-category-ov"):
if rule.meta.maec.malware_category:
continue
if rule.meta.maec.malware_category_ov:
continue
yield rule
class StringIO(six.StringIO):
class StringIO(io.StringIO):
def writeln(self, s):
self.write(s)
self.write("\n")

View File

@@ -3,7 +3,7 @@ example::
send data
namespace communication
author william.ballenthin@fireeye.com
author william.ballenthin@mandiant.com
description all known techniques for sending data to a potential C2 server
scope function
examples BFB9B5391A13D0AFD787E87AB90F14F5:0x13145D60
@@ -14,7 +14,7 @@ example::
0x10003415
0x10003797
Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at: [package root]/LICENSE.txt
@@ -22,13 +22,38 @@ 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 enum
import tabulate
import capa.rules
import capa.helpers
import capa.render.utils as rutils
import capa.features.freeze as frz
import capa.render.result_document as rd
from capa.rules import RuleSet
from capa.engine import MatchResults
def render_meta(ostream, doc):
def format_address(address: frz.Address) -> str:
if address.type == frz.AddressType.ABSOLUTE:
return capa.helpers.hex(address.value)
elif address.type == frz.AddressType.RELATIVE:
return f"base address+{capa.helpers.hex(address.value)}"
elif address.type == frz.AddressType.FILE:
return f"file+{capa.helpers.hex(address.value)}"
elif address.type == frz.AddressType.DN_TOKEN:
return f"token({capa.helpers.hex(address.value)})"
elif address.type == frz.AddressType.DN_TOKEN_OFFSET:
token, offset = address.value
return f"token({capa.helpers.hex(token)})+{capa.helpers.hex(offset)}"
elif address.type == frz.AddressType.NO_ADDRESS:
return "global"
else:
raise ValueError("unexpected address type")
def render_meta(ostream, doc: rd.ResultDocument):
"""
like:
@@ -38,7 +63,9 @@ def render_meta(ostream, doc):
path /tmp/suspicious.dll_
timestamp 2020-07-03T10:17:05.796933
capa version 0.0.0
format auto
os windows
format pe
arch amd64
extractor VivisectFeatureExtractor
base address 0x10000000
rules (embedded rules)
@@ -46,27 +73,31 @@ def render_meta(ostream, doc):
total feature count 1918
"""
rows = [
("md5", doc["meta"]["sample"]["md5"]),
("sha1", doc["meta"]["sample"]["sha1"]),
("sha256", doc["meta"]["sample"]["sha256"]),
("path", doc["meta"]["sample"]["path"]),
("timestamp", doc["meta"]["timestamp"]),
("capa version", doc["meta"]["version"]),
("format", doc["meta"]["analysis"]["format"]),
("extractor", doc["meta"]["analysis"]["extractor"]),
("base address", hex(doc["meta"]["analysis"]["base_address"])),
("rules", doc["meta"]["analysis"]["rules"]),
("function count", len(doc["meta"]["analysis"]["feature_counts"]["functions"])),
("md5", doc.meta.sample.md5),
("sha1", doc.meta.sample.sha1),
("sha256", doc.meta.sample.sha256),
("path", doc.meta.sample.path),
("timestamp", doc.meta.timestamp),
("capa version", doc.meta.version),
("os", doc.meta.analysis.os),
("format", doc.meta.analysis.format),
("arch", doc.meta.analysis.arch),
("extractor", doc.meta.analysis.extractor),
("base address", format_address(doc.meta.analysis.base_address)),
("rules", "\n".join(doc.meta.analysis.rules)),
("function count", len(doc.meta.analysis.feature_counts.functions)),
("library function count", len(doc.meta.analysis.library_functions)),
(
"total feature count",
doc["meta"]["analysis"]["feature_counts"]["file"]
+ sum(doc["meta"]["analysis"]["feature_counts"]["functions"].values()),
doc.meta.analysis.feature_counts.file
+ sum(map(lambda f: f.count, doc.meta.analysis.feature_counts.functions)),
),
]
ostream.writeln(tabulate.tabulate(rows, tablefmt="plain"))
def render_rules(ostream, doc):
def render_rules(ostream, doc: rd.ResultDocument):
"""
like:
@@ -79,28 +110,32 @@ def render_rules(ostream, doc):
"""
had_match = False
for rule in rutils.capability_rules(doc):
count = len(rule["matches"])
count = len(rule.matches)
if count == 1:
capability = rutils.bold(rule["meta"]["name"])
capability = rutils.bold(rule.meta.name)
else:
capability = "%s (%d matches)" % (rutils.bold(rule["meta"]["name"]), count)
capability = "%s (%d matches)" % (rutils.bold(rule.meta.name), count)
ostream.writeln(capability)
had_match = True
rows = []
for key in ("namespace", "description", "scope"):
if key == "name" or key not in rule["meta"]:
v = getattr(rule.meta, key)
if not v:
continue
v = rule["meta"][key]
if isinstance(v, list) and len(v) == 1:
v = v[0]
if isinstance(v, enum.Enum):
v = v.value
rows.append((key, v))
if rule["meta"]["scope"] != capa.rules.FILE_SCOPE:
locations = doc["rules"][rule["meta"]["name"]]["matches"].keys()
rows.append(("matches", "\n".join(map(rutils.hex, locations))))
if rule.meta.scope != capa.rules.FILE_SCOPE:
locations = list(map(lambda m: m[0], doc.rules[rule.meta.name].matches))
rows.append(("matches", "\n".join(map(format_address, locations))))
ostream.writeln(tabulate.tabulate(rows, tablefmt="plain"))
ostream.write("\n")
@@ -109,7 +144,7 @@ def render_rules(ostream, doc):
ostream.writeln(rutils.bold("no capabilities found"))
def render_verbose(doc):
def render_verbose(doc: rd.ResultDocument):
ostream = rutils.StringIO()
render_meta(ostream, doc)
@@ -119,3 +154,7 @@ def render_verbose(doc):
ostream.write("\n")
return ostream.getvalue()
def render(meta, rules: RuleSet, capabilities: MatchResults) -> str:
return render_verbose(rd.ResultDocument.from_capa(meta, rules, capabilities))

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2020 FireEye, Inc. All Rights Reserved.
# Copyright (C) 2020 Mandiant, Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at: [package root]/LICENSE.txt
@@ -6,109 +6,201 @@
# 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 collections
from typing import Dict, Iterable
import tabulate
import capa.rules
import capa.helpers
import capa.render.utils as rutils
import capa.render.verbose
import capa.features.common
import capa.features.freeze as frz
import capa.features.address
import capa.render.result_document as rd
import capa.features.freeze.features as frzf
from capa.rules import RuleSet
from capa.engine import MatchResults
def render_locations(ostream, match):
def render_locations(ostream, locations: Iterable[frz.Address]):
import capa.render.verbose as v
# its possible to have an empty locations array here,
# such as when we're in MODE_FAILURE and showing the logic
# under a `not` statement (which will have no matched locations).
locations = list(sorted(match.get("locations", [])))
locations = list(sorted(locations))
if len(locations) == 0:
return
ostream.write(" @ ")
if len(locations) == 1:
ostream.write(" @ ")
ostream.write(rutils.hex(locations[0]))
ostream.write(v.format_address(locations[0]))
elif len(locations) > 4:
# don't display too many locations, because it becomes very noisy.
# probably only the first handful of locations will be useful for inspection.
ostream.write(", ".join(map(v.format_address, locations[0:4])))
ostream.write(", and %d more..." % (len(locations) - 4))
elif len(locations) > 1:
ostream.write(" @ ")
if len(locations) > 4:
# don't display too many locations, because it becomes very noisy.
# probably only the first handful of locations will be useful for inspection.
ostream.write(", ".join(map(rutils.hex, locations[0:4])))
ostream.write(", and %d more..." % (len(locations) - 4))
else:
ostream.write(", ".join(map(rutils.hex, locations)))
ostream.write(", ".join(map(v.format_address, locations)))
else:
raise RuntimeError("unreachable")
def render_statement(ostream, match, statement, indent=0):
def render_statement(ostream, match: rd.Match, statement: rd.Statement, indent=0):
ostream.write(" " * indent)
if statement["type"] in ("and", "or", "optional", "not", "subscope"):
ostream.write(statement["type"])
if isinstance(statement, rd.SubscopeStatement):
# emit `basic block:`
# rather than `subscope:`
ostream.write(statement.scope)
ostream.write(":")
if statement.get("description"):
ostream.write(" = %s" % statement["description"])
if statement.description:
ostream.write(" = %s" % statement.description)
ostream.writeln("")
elif statement["type"] == "some":
ostream.write("%d or more:" % (statement["count"]))
if statement.get("description"):
ostream.write(" = %s" % statement["description"])
elif isinstance(statement, (rd.CompoundStatement)):
# emit `and:` `or:` `optional:` `not:`
ostream.write(statement.type)
ostream.write(":")
if statement.description:
ostream.write(" = %s" % statement.description)
ostream.writeln("")
elif statement["type"] == "range":
elif isinstance(statement, rd.SomeStatement):
ostream.write("%d or more:" % (statement.count))
if statement.description:
ostream.write(" = %s" % statement.description)
ostream.writeln("")
elif isinstance(statement, rd.RangeStatement):
# `range` is a weird node, its almost a hybrid of statement+feature.
# it is a specific feature repeated multiple times.
# there's no additional logic in the feature part, just the existence of a feature.
# so, we have to inline some of the feature rendering here.
child = statement["child"]
child = statement.child
value = child.dict(by_alias=True).get(child.type)
if child[child["type"]]:
value = rutils.bold2(child[child["type"]])
if child.get("description"):
ostream.write("count(%s(%s = %s)): " % (child["type"], value, child["description"]))
if value:
if isinstance(child, frzf.StringFeature):
value = '"%s"' % capa.features.common.escape_string(value)
value = rutils.bold2(value)
if child.description:
ostream.write("count(%s(%s = %s)): " % (child.type, value, child.description))
else:
ostream.write("count(%s(%s)): " % (child["type"], value))
ostream.write("count(%s(%s)): " % (child.type, value))
else:
ostream.write("count(%s): " % child["type"])
ostream.write("count(%s): " % child.type)
if statement["max"] == statement["min"]:
ostream.write("%d" % (statement["min"]))
elif statement["min"] == 0:
ostream.write("%d or fewer" % (statement["max"]))
elif statement["max"] == (1 << 64 - 1):
ostream.write("%d or more" % (statement["min"]))
if statement.max == statement.min:
ostream.write("%d" % (statement.min))
elif statement.min == 0:
ostream.write("%d or fewer" % (statement.max))
elif statement.max == (1 << 64 - 1):
ostream.write("%d or more" % (statement.min))
else:
ostream.write("between %d and %d" % (statement["min"], statement["max"]))
ostream.write("between %d and %d" % (statement.min, statement.max))
if statement.get("description"):
ostream.write(" = %s" % statement["description"])
render_locations(ostream, match)
if statement.description:
ostream.write(" = %s" % statement.description)
render_locations(ostream, match.locations)
ostream.writeln("")
else:
raise RuntimeError("unexpected match statement type: " + str(statement))
def render_feature(ostream, match, feature, indent=0):
def render_string_value(s: str) -> str:
return '"%s"' % capa.features.common.escape_string(s)
def render_feature(ostream, match: rd.Match, feature: frzf.Feature, indent=0):
ostream.write(" " * indent)
key = feature["type"]
value = feature[feature["type"]]
if key == "regex":
key = "string" # render string for regex to mirror the rule source
value = feature["match"] # the match provides more information than the value for regex
key = feature.type
if isinstance(feature, frzf.BasicBlockFeature):
# i don't think it makes sense to have standalone basic block features.
# we don't parse them from rules, only things like: `count(basic block) > 1`
raise ValueError("cannot render basic block feature directly")
elif isinstance(feature, frzf.ImportFeature):
# fixup access to Python reserved name
value = feature.import_
elif isinstance(feature, frzf.ClassFeature):
value = feature.class_
else:
# convert attributes to dictionary using aliased names, if applicable
value = feature.dict(by_alias=True).get(key, None)
ostream.write(key)
ostream.write(": ")
if value is None:
raise ValueError("%s contains None" % key)
if value:
ostream.write(rutils.bold2(value))
if not isinstance(feature, (frzf.RegexFeature, frzf.SubstringFeature)):
# like:
# number: 10 = SOME_CONSTANT @ 0x401000
if isinstance(feature, frzf.StringFeature):
value = render_string_value(value)
if "description" in feature:
ostream.write(capa.rules.DESCRIPTION_SEPARATOR)
ostream.write(feature["description"])
elif isinstance(
feature, (frzf.NumberFeature, frzf.OffsetFeature, frzf.OperandNumberFeature, frzf.OperandOffsetFeature)
):
assert isinstance(value, int)
value = capa.helpers.hex(value)
render_locations(ostream, match)
ostream.write("\n")
if isinstance(feature, frzf.PropertyFeature) and feature.access is not None:
key = f"property/{feature.access}"
elif isinstance(feature, frzf.OperandNumberFeature):
key = f"operand[{feature.index}].number"
elif isinstance(feature, frzf.OperandOffsetFeature):
key = f"operand[{feature.index}].offset"
ostream.write(f"{key}: ")
if value:
ostream.write(rutils.bold2(value))
if feature.description:
ostream.write(capa.rules.DESCRIPTION_SEPARATOR)
ostream.write(feature.description)
if not isinstance(feature, (frzf.OSFeature, frzf.ArchFeature, frzf.FormatFeature)):
render_locations(ostream, match.locations)
ostream.write("\n")
else:
# like:
# regex: /blah/ = SOME_CONSTANT
# - "foo blah baz" @ 0x401000
# - "aaa blah bbb" @ 0x402000, 0x403400
ostream.write(key)
ostream.write(": ")
ostream.write(value)
ostream.write("\n")
for capture, locations in sorted(match.captures.items()):
ostream.write(" " * (indent + 1))
ostream.write("- ")
ostream.write(rutils.bold2(render_string_value(capture)))
render_locations(ostream, locations)
ostream.write("\n")
def render_node(ostream, match, node, indent=0):
if node["type"] == "statement":
render_statement(ostream, match, node["statement"], indent=indent)
elif node["type"] == "feature":
render_feature(ostream, match, node["feature"], indent=indent)
def render_node(ostream, match: rd.Match, node: rd.Node, indent=0):
if isinstance(node, rd.StatementNode):
render_statement(ostream, match, node.statement, indent=indent)
elif isinstance(node, rd.FeatureNode):
render_feature(ostream, match, node.feature, indent=indent)
else:
raise RuntimeError("unexpected node type: " + str(node))
@@ -121,105 +213,166 @@ MODE_SUCCESS = "success"
MODE_FAILURE = "failure"
def render_match(ostream, match, indent=0, mode=MODE_SUCCESS):
def render_match(ostream, match: rd.Match, indent=0, mode=MODE_SUCCESS):
child_mode = mode
if mode == MODE_SUCCESS:
# display only nodes that evaluated successfully.
if not match["success"]:
if not match.success:
return
# optional statement with no successful children is empty
if match["node"].get("statement", {}).get("type") == "optional" and not any(
map(lambda m: m["success"], match["children"])
):
return
if isinstance(match.node, rd.StatementNode) and match.node.statement.type == rd.CompoundStatementType.OPTIONAL:
if not any(map(lambda m: m.success, match.children)):
return
# not statement, so invert the child mode to show failed evaluations
if match["node"].get("statement", {}).get("type") == "not":
if isinstance(match.node, rd.StatementNode) and match.node.statement.type == rd.CompoundStatementType.NOT:
child_mode = MODE_FAILURE
elif mode == MODE_FAILURE:
# display only nodes that did not evaluate to True
if match["success"]:
if match.success:
return
# optional statement with successful children is not relevant
if match["node"].get("statement", {}).get("type") == "optional" and any(
map(lambda m: m["success"], match["children"])
):
return
if isinstance(match.node, rd.StatementNode) and match.node.statement.type == rd.CompoundStatementType.OPTIONAL:
if any(map(lambda m: m.success, match.children)):
return
# not statement, so invert the child mode to show successful evaluations
if match["node"].get("statement", {}).get("type") == "not":
if isinstance(match.node, rd.StatementNode) and match.node.statement.type == rd.CompoundStatementType.NOT:
child_mode = MODE_SUCCESS
else:
raise RuntimeError("unexpected mode: " + mode)
render_node(ostream, match, match["node"], indent=indent)
render_node(ostream, match, match.node, indent=indent)
for child in match["children"]:
for child in match.children:
render_match(ostream, child, indent=indent + 1, mode=child_mode)
def render_rules(ostream, doc):
def render_rules(ostream, doc: rd.ResultDocument):
"""
like:
## rules
check for OutputDebugString error
namespace anti-analysis/anti-debugging/debugger-detection
author michael.hunhoff@fireeye.com
author michael.hunhoff@mandiant.com
scope function
mbc Anti-Behavioral Analysis::Detect Debugger::OutputDebugString
examples Practical Malware Analysis Lab 16-02.exe_:0x401020
function @ 0x10004706
and:
api: kernel32.SetLastError @ 0x100047C2
api: kernel32.GetLastError @ 0x10004A87
api: kernel32.OutputDebugString @ 0x10004767, 0x10004787, 0x10004816, 0x10004895
"""
functions_by_bb: Dict[capa.features.address.Address, capa.features.address.Address] = {}
for finfo in doc.meta.analysis.layout.functions:
faddress = finfo.address.to_capa()
for bb in finfo.matched_basic_blocks:
bbaddress = bb.address.to_capa()
functions_by_bb[bbaddress] = faddress
had_match = False
for rule in rutils.capability_rules(doc):
count = len(rule["matches"])
for _, _, rule in sorted(map(lambda rule: (rule.meta.namespace or "", rule.meta.name, rule), doc.rules.values())):
# default scope hides things like lib rules, malware-category rules, etc.
# but in vverbose mode, we really want to show everything.
#
# still ignore subscope rules because they're stitched into the final document.
if rule.meta.is_subscope_rule:
continue
lib_info = ""
count = len(rule.matches)
if count == 1:
capability = rutils.bold(rule["meta"]["name"])
if rule.meta.lib:
lib_info = " (library rule)"
capability = "%s%s" % (rutils.bold(rule.meta.name), lib_info)
else:
capability = "%s (%d matches)" % (rutils.bold(rule["meta"]["name"]), count)
if rule.meta.lib:
lib_info = ", only showing first match of library rule"
capability = "%s (%d matches%s)" % (rutils.bold(rule.meta.name), count, lib_info)
ostream.writeln(capability)
had_match = True
rows = []
for key in capa.rules.META_KEYS:
if key == "name" or key not in rule["meta"]:
continue
if not rule.meta.lib:
# library rules should not have a namespace
rows.append(("namespace", rule.meta.namespace))
v = rule["meta"][key]
if isinstance(v, list) and len(v) == 1:
v = v[0]
elif isinstance(v, list) and len(v) > 1:
v = ", ".join(v)
rows.append((key, v))
if rule.meta.maec.analysis_conclusion or rule.meta.maec.analysis_conclusion_ov:
rows.append(
(
"maec/analysis-conclusion",
rule.meta.maec.analysis_conclusion or rule.meta.maec.analysis_conclusion_ov,
)
)
if rule.meta.maec.malware_family:
rows.append(("maec/malware-family", rule.meta.maec.malware_family))
if rule.meta.maec.malware_category or rule.meta.maec.malware_category_ov:
rows.append(
("maec/malware-category", rule.meta.maec.malware_category or rule.meta.maec.malware_category_ov)
)
rows.append(("author", ", ".join(rule.meta.authors)))
rows.append(("scope", rule.meta.scope.value))
if rule.meta.attack:
rows.append(("att&ck", ", ".join([rutils.format_parts_id(v) for v in rule.meta.attack])))
if rule.meta.mbc:
rows.append(("mbc", ", ".join([rutils.format_parts_id(v) for v in rule.meta.mbc])))
if rule.meta.references:
rows.append(("references", ", ".join(rule.meta.references)))
if rule.meta.description:
rows.append(("description", rule.meta.description))
ostream.writeln(tabulate.tabulate(rows, tablefmt="plain"))
if rule["meta"]["scope"] == capa.rules.FILE_SCOPE:
matches = list(doc["rules"][rule["meta"]["name"]]["matches"].values())
if rule.meta.scope == capa.rules.FILE_SCOPE:
matches = doc.rules[rule.meta.name].matches
if len(matches) != 1:
# i think there should only ever be one match per file-scope rule,
# because we do the file-scope evaluation a single time.
# but i'm not 100% sure if this is/will always be true.
# so, lets be explicit about our assumptions and raise an exception if they fail.
raise RuntimeError("unexpected file scope match count: %d" % (len(matches)))
render_match(ostream, matches[0], indent=0)
first_address, first_match = matches[0]
render_match(ostream, first_match, indent=0)
else:
for location, match in sorted(doc["rules"][rule["meta"]["name"]]["matches"].items()):
ostream.write(rule["meta"]["scope"])
for location, match in sorted(doc.rules[rule.meta.name].matches):
ostream.write(rule.meta.scope)
ostream.write(" @ ")
ostream.writeln(rutils.hex(location))
ostream.write(capa.render.verbose.format_address(location))
if rule.meta.scope == capa.rules.BASIC_BLOCK_SCOPE:
ostream.write(
" in function "
+ capa.render.verbose.format_address(frz.Address.from_capa(functions_by_bb[location.to_capa()]))
)
ostream.write("\n")
render_match(ostream, match, indent=1)
if rule.meta.lib:
# only show first match
break
ostream.write("\n")
if not had_match:
ostream.writeln(rutils.bold("no capabilities found"))
def render_vverbose(doc):
def render_vverbose(doc: rd.ResultDocument):
ostream = rutils.StringIO()
capa.render.verbose.render_meta(ostream, doc)
@@ -229,3 +382,7 @@ def render_vverbose(doc):
ostream.write("\n")
return ostream.getvalue()
def render(meta, rules: RuleSet, capabilities: MatchResults) -> str:
return render_vverbose(rd.ResultDocument.from_capa(meta, rules, capabilities))

View File

@@ -1,848 +0,0 @@
# Copyright (C) 2020 FireEye, 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 uuid
import codecs
import logging
import binascii
import functools
import six
import ruamel.yaml
import capa.engine
import capa.features
import capa.features.file
import capa.features.insn
import capa.features.basicblock
from capa.engine import *
from capa.features import MAX_BYTES_FEATURE_SIZE
logger = logging.getLogger(__name__)
# these are the standard metadata fields, in the preferred order.
# when reformatted, any custom keys will come after these.
META_KEYS = (
"name",
"namespace",
"rule-category",
"maec/analysis-conclusion",
"maec/analysis-conclusion-ov",
"maec/malware-category",
"maec/malware-category-ov",
"author",
"description",
"lib",
"scope",
"att&ck",
"mbc",
"references",
"examples",
)
# these are meta fields that are internal to capa,
# and added during rule reading/construction.
# they may help use manipulate or index rules,
# but should not be exposed to clients.
HIDDEN_META_KEYS = ("capa/nursery", "capa/path")
FILE_SCOPE = "file"
FUNCTION_SCOPE = "function"
BASIC_BLOCK_SCOPE = "basic block"
SUPPORTED_FEATURES = {
FILE_SCOPE: {
capa.features.MatchedRule,
capa.features.file.Export,
capa.features.file.Import,
capa.features.file.Section,
capa.features.Characteristic("embedded pe"),
capa.features.String,
},
FUNCTION_SCOPE: {
# plus basic block scope features, see below
capa.features.basicblock.BasicBlock,
capa.features.Characteristic("calls from"),
capa.features.Characteristic("calls to"),
capa.features.Characteristic("loop"),
capa.features.Characteristic("recursive call"),
},
BASIC_BLOCK_SCOPE: {
capa.features.MatchedRule,
capa.features.insn.API,
capa.features.insn.Number,
capa.features.String,
capa.features.Bytes,
capa.features.insn.Offset,
capa.features.insn.Mnemonic,
capa.features.Characteristic("nzxor"),
capa.features.Characteristic("peb access"),
capa.features.Characteristic("fs access"),
capa.features.Characteristic("gs access"),
capa.features.Characteristic("cross section flow"),
capa.features.Characteristic("tight loop"),
capa.features.Characteristic("stack string"),
capa.features.Characteristic("indirect call"),
},
}
# all basic block scope features are also function scope features
SUPPORTED_FEATURES[FUNCTION_SCOPE].update(SUPPORTED_FEATURES[BASIC_BLOCK_SCOPE])
class InvalidRule(ValueError):
def __init__(self, msg):
super(InvalidRule, self).__init__()
self.msg = msg
def __str__(self):
return "invalid rule: %s" % (self.msg)
def __repr__(self):
return str(self)
class InvalidRuleWithPath(InvalidRule):
def __init__(self, path, msg):
super(InvalidRuleWithPath, self).__init__(msg)
self.path = path
self.msg = msg
self.__cause__ = None
def __str__(self):
return "invalid rule: %s: %s" % (self.path, self.msg)
class InvalidRuleSet(ValueError):
def __init__(self, msg):
super(InvalidRuleSet, self).__init__()
self.msg = msg
def __str__(self):
return "invalid rule set: %s" % (self.msg)
def __repr__(self):
return str(self)
def ensure_feature_valid_for_scope(scope, feature):
if isinstance(feature, capa.features.Characteristic):
if capa.features.Characteristic(feature.value) not in SUPPORTED_FEATURES[scope]:
raise InvalidRule("feature %s not support for scope %s" % (feature, scope))
elif not isinstance(feature, tuple(filter(lambda t: isinstance(t, type), SUPPORTED_FEATURES[scope]))):
raise InvalidRule("feature %s not support for scope %s" % (feature, scope))
def parse_int(s):
if s.startswith("0x"):
return int(s, 0x10)
else:
return int(s, 10)
def parse_range(s):
"""
parse a string "(0, 1)" into a range (min, max).
min and/or max may by None to indicate an unbound range.
"""
# we want to use `{` characters, but this is a dict in yaml.
if not s.startswith("("):
raise InvalidRule("invalid range: %s" % (s))
if not s.endswith(")"):
raise InvalidRule("invalid range: %s" % (s))
s = s[len("(") : -len(")")]
min, _, max = s.partition(",")
min = min.strip()
max = max.strip()
if min:
min = parse_int(min.strip())
if min < 0:
raise InvalidRule("range min less than zero")
else:
min = None
if max:
max = parse_int(max.strip())
if max < 0:
raise InvalidRule("range max less than zero")
else:
max = None
if min is not None and max is not None:
if max < min:
raise InvalidRule("range max less than min")
return min, max
def parse_feature(key):
# keep this in sync with supported features
if key == "api":
return capa.features.insn.API
elif key == "string":
return capa.features.StringFactory
elif key == "bytes":
return capa.features.Bytes
elif key == "number":
return capa.features.insn.Number
elif key.startswith("number/"):
arch = key.partition("/")[2]
# the other handlers here return constructors for features,
# and we want to as well,
# however, we need to preconfigure one of the arguments (`arch`).
# so, instead we return a partially-applied function that
# provides `arch` to the feature constructor.
# it forwards any other arguments provided to the closure along to the constructor.
return functools.partial(capa.features.insn.Number, arch=arch)
elif key == "offset":
return capa.features.insn.Offset
elif key.startswith("offset/"):
arch = key.partition("/")[2]
return functools.partial(capa.features.insn.Offset, arch=arch)
elif key == "mnemonic":
return capa.features.insn.Mnemonic
elif key == "basic blocks":
return capa.features.basicblock.BasicBlock
elif key == "characteristic":
return capa.features.Characteristic
elif key == "export":
return capa.features.file.Export
elif key == "import":
return capa.features.file.Import
elif key == "section":
return capa.features.file.Section
elif key == "match":
return capa.features.MatchedRule
else:
raise InvalidRule("unexpected statement: %s" % key)
# this is the separator between a feature value and its description
# when using the inline description syntax, like:
#
# number: 42 = ENUM_FAVORITE_NUMBER
DESCRIPTION_SEPARATOR = " = "
def parse_description(s, value_type, description=None):
"""
s can be an int or a string
"""
if value_type != "string" and isinstance(s, six.string_types) and DESCRIPTION_SEPARATOR in s:
if description:
raise InvalidRule(
'unexpected value: "%s", only one description allowed (inline description with `%s`)'
% (s, DESCRIPTION_SEPARATOR)
)
value, _, description = s.partition(DESCRIPTION_SEPARATOR)
if description == "":
raise InvalidRule('unexpected value: "%s", description cannot be empty' % s)
else:
value = s
if isinstance(value, six.string_types):
if value_type == "bytes":
try:
value = codecs.decode(value.replace(" ", ""), "hex")
# TODO: Remove TypeError when Python2 is not used anymore
except (TypeError, binascii.Error):
raise InvalidRule('unexpected bytes value: "%s", must be a valid hex sequence' % value)
if len(value) > MAX_BYTES_FEATURE_SIZE:
raise InvalidRule(
"unexpected bytes value: byte sequences must be no larger than %s bytes" % MAX_BYTES_FEATURE_SIZE
)
elif value_type in ("number", "offset") or value_type.startswith(("number/", "offset/")):
try:
value = parse_int(value)
except ValueError:
raise InvalidRule('unexpected value: "%s", must begin with numerical value' % value)
return value, description
def build_statements(d, scope):
if len(d.keys()) > 2:
raise InvalidRule("too many statements")
key = list(d.keys())[0]
if key == "and":
return And([build_statements(dd, scope) for dd in d[key]], description=d.get("description"))
elif key == "or":
return Or([build_statements(dd, scope) for dd in d[key]], description=d.get("description"))
elif key == "not":
if len(d[key]) != 1:
raise InvalidRule("not statement must have exactly one child statement")
return Not(build_statements(d[key][0], scope), description=d.get("description"))
elif key.endswith(" or more"):
count = int(key[: -len("or more")])
return Some(count, [build_statements(dd, scope) for dd in d[key]], description=d.get("description"))
elif key == "optional":
# `optional` is an alias for `0 or more`
# which is useful for documenting behaviors,
# like with `write file`, we might say that `WriteFile` is optionally found alongside `CreateFileA`.
return Some(0, [build_statements(dd, scope) for dd in d[key]], description=d.get("description"))
elif key == "function":
if scope != FILE_SCOPE:
raise InvalidRule("function subscope supported only for file scope")
if len(d[key]) != 1:
raise InvalidRule("subscope must have exactly one child statement")
return Subscope(FUNCTION_SCOPE, build_statements(d[key][0], FUNCTION_SCOPE))
elif key == "basic block":
if scope != FUNCTION_SCOPE:
raise InvalidRule("basic block subscope supported only for function scope")
if len(d[key]) != 1:
raise InvalidRule("subscope must have exactly one child statement")
return Subscope(BASIC_BLOCK_SCOPE, build_statements(d[key][0], BASIC_BLOCK_SCOPE))
elif key.startswith("count(") and key.endswith(")"):
# e.g.:
#
# count(basic block)
# count(mnemonic(mov))
# count(characteristic(nzxor))
term = key[len("count(") : -len(")")]
# when looking for the existence of such a feature, our rule might look like:
# - mnemonic: mov
#
# but here we deal with the form: `mnemonic(mov)`.
term, _, arg = term.partition("(")
Feature = parse_feature(term)
if arg:
arg = arg[: -len(")")]
# can't rely on yaml parsing ints embedded within strings
# like:
#
# count(offset(0xC))
# count(number(0x11223344))
# count(number(0x100 = description))
if term != "string":
value, description = parse_description(arg, term)
feature = Feature(value, description=description)
else:
# arg is string (which doesn't support inline descriptions), like:
#
# count(string(error))
# TODO: what about embedded newlines?
feature = Feature(arg)
else:
feature = Feature()
ensure_feature_valid_for_scope(scope, feature)
count = d[key]
if isinstance(count, int):
return Range(feature, min=count, max=count, description=d.get("description"))
elif count.endswith(" or more"):
min = parse_int(count[: -len(" or more")])
max = None
return Range(feature, min=min, max=max, description=d.get("description"))
elif count.endswith(" or fewer"):
min = None
max = parse_int(count[: -len(" or fewer")])
return Range(feature, min=min, max=max, description=d.get("description"))
elif count.startswith("("):
min, max = parse_range(count)
return Range(feature, min=min, max=max, description=d.get("description"))
else:
raise InvalidRule("unexpected range: %s" % (count))
elif key == "string" and not isinstance(d[key], six.string_types):
raise InvalidRule("ambiguous string value %s, must be defined as explicit string" % d[key])
else:
Feature = parse_feature(key)
value, description = parse_description(d[key], key, d.get("description"))
try:
feature = Feature(value, description=description)
except ValueError as e:
raise InvalidRule(str(e))
ensure_feature_valid_for_scope(scope, feature)
return feature
def first(s):
return s[0]
def second(s):
return s[1]
# we use the ruamel.yaml parser because it supports roundtripping of documents with comments.
yaml = ruamel.yaml.YAML(typ="rt")
# use block mode, not inline json-like mode
yaml.default_flow_style = False
# indent lists by two spaces below their parent
#
# features:
# - or:
# - mnemonic: aesdec
# - mnemonic: vaesdec
yaml.indent(sequence=2, offset=2)
# avoid word wrapping
yaml.width = 4096
class Rule(object):
def __init__(self, name, scope, statement, meta, definition=""):
super(Rule, self).__init__()
self.name = name
self.scope = scope
self.statement = statement
self.meta = meta
self.definition = definition
def __str__(self):
return "Rule(name=%s)" % (self.name)
def __repr__(self):
return "Rule(scope=%s, name=%s)" % (self.scope, self.name)
def get_dependencies(self, namespaces):
"""
fetch the names of rules this rule relies upon.
these are only the direct dependencies; a user must
compute the transitive dependency graph themself, if they want it.
Args:
namespaces(Dict[str, List[Rule]]): mapping from namespace name to rules in it.
see `index_rules_by_namespace`.
Returns:
List[str]: names of rules upon which this rule depends.
"""
deps = set([])
def rec(statement):
if isinstance(statement, capa.features.MatchedRule):
# we're not sure at this point if the `statement.value` is
# really a rule name or a namespace name (we use `MatchedRule` for both cases).
# we'll give precedence to namespaces, and then assume if that does work,
# that it must be a rule name.
#
# we don't expect any collisions between namespaces and rule names, but its possible.
# most likely would be collision between top level namespace (e.g. `host-interaction`) and rule name.
# but, namespaces tend to use `-` while rule names use ` `. so, unlikely, but possible.
if statement.value in namespaces:
# matches a namespace, so take precedence and don't even check rule names.
deps.update(map(lambda r: r.name, namespaces[statement.value]))
else:
# not a namespace, assume its a rule name.
deps.add(statement.value)
elif isinstance(statement, Statement):
for child in statement.get_children():
rec(child)
# else: might be a Feature, etc.
# which we don't care about here.
rec(self.statement)
return deps
def _extract_subscope_rules_rec(self, statement):
if isinstance(statement, Statement):
# for each child that is a subscope,
for subscope in filter(
lambda statement: isinstance(statement, capa.engine.Subscope), statement.get_children()
):
# create a new rule from it.
# the name is a randomly generated, hopefully unique value.
# ideally, this won't every be rendered to a user.
name = self.name + "/" + uuid.uuid4().hex
new_rule = Rule(
name,
subscope.scope,
subscope.child,
{
"name": name,
"scope": subscope.scope,
# these derived rules are never meant to be inspected separately,
# they are dependencies for the parent rule,
# so mark it as such.
"lib": True,
# metadata that indicates this is derived from a subscope statement
"capa/subscope-rule": True,
# metadata that links the child rule the parent rule
"capa/parent": self.name,
},
)
# update the existing statement to `match` the new rule
new_node = capa.features.MatchedRule(name)
statement.replace_child(subscope, new_node)
# and yield the new rule to our caller
yield new_rule
# now recurse to other nodes in the logic tree.
# note: we cannot recurse into the subscope sub-tree,
# because its been replaced by a `match` statement.
for child in statement.get_children():
for new_rule in self._extract_subscope_rules_rec(child):
yield new_rule
def extract_subscope_rules(self):
"""
scan through the statements of this rule,
replacing subscope statements with `match` references to a newly created rule,
which are yielded from this routine.
note: this mutates the current rule.
example::
for derived_rule in rule.extract_subscope_rules():
assert derived_rule.meta['capa/parent'] == rule.name
"""
# recurse through statements
# when encounter Subscope statement
# create new transient rule
# copy logic into the new rule
# replace old node with reference to new rule
# yield new rule
for new_rule in self._extract_subscope_rules_rec(self.statement):
yield new_rule
def evaluate(self, features):
return self.statement.evaluate(features)
@classmethod
def from_dict(cls, d, s):
name = d["rule"]["meta"]["name"]
# if scope is not specified, default to function scope.
# this is probably the mode that rule authors will start with.
scope = d["rule"]["meta"].get("scope", FUNCTION_SCOPE)
statements = d["rule"]["features"]
# the rule must start with a single logic node.
# doing anything else is too implicit and difficult to remove (AND vs OR ???).
if len(statements) != 1:
raise InvalidRule("rule must begin with a single top level statement")
if isinstance(statements[0], capa.engine.Subscope):
raise InvalidRule("top level statement may not be a subscope")
if scope not in SUPPORTED_FEATURES.keys():
raise InvalidRule("{:s} is not a supported scope".format(scope))
return cls(name, scope, build_statements(statements[0], scope), d["rule"]["meta"], s)
@classmethod
def from_yaml(cls, s):
return cls.from_dict(yaml.load(s), s)
@classmethod
def from_yaml_file(cls, path):
with open(path, "rb") as f:
try:
return cls.from_yaml(f.read().decode("utf-8"))
except InvalidRule as e:
raise InvalidRuleWithPath(path, str(e))
def to_yaml(self):
# reformat the yaml document with a common style.
# this includes:
# - ordering the meta elements
# - indenting the nested items with two spaces
#
# updates to the rule will be synced for meta fields,
# but not for rule logic.
# programmatic generation of rules is not yet supported.
definition = yaml.load(self.definition)
# definition retains a reference to `meta`,
# so we're updating that in place.
definition["rule"]["meta"] = self.meta
meta = self.meta
meta["name"] = self.name
meta["scope"] = self.scope
def move_to_end(m, k):
# ruamel.yaml uses an ordereddict-like structure to track maps (CommentedMap).
# here we refresh the insertion order of the given key.
# this will move it to the end of the sequence.
v = m[k]
del m[k]
m[k] = v
move_to_end(definition["rule"], "meta")
move_to_end(definition["rule"], "features")
for key in META_KEYS:
if key in meta:
move_to_end(meta, key)
for key in sorted(meta.keys()):
if key in META_KEYS:
continue
move_to_end(meta, key)
# save off the existing hidden meta values,
# emit the document,
# and re-add the hidden meta.
hidden_meta = {}
for key in HIDDEN_META_KEYS:
value = meta.get(key)
if value:
hidden_meta[key] = value
for key in hidden_meta.keys():
del meta[key]
ostream = six.BytesIO()
yaml.dump(definition, ostream)
for key, value in hidden_meta.items():
if value is None:
continue
meta[key] = value
doc = ostream.getvalue().decode("utf-8").rstrip("\n") + "\n"
# when we have something like:
#
# and:
# - string: foo
# description: bar
#
# we want the `description` horizontally aligned with the start of the `string` (like above).
# however, ruamel will give us (which I don't think is even valid yaml):
#
# and:
# - string: foo
# description: bar
#
# tweaking `ruamel.indent()` doesn't quite give us the control we want.
# so, add the two extra spaces that we've determined we need through experimentation.
# see #263
doc = doc.replace(" description:", " description:")
return doc
def get_rules_with_scope(rules, scope):
"""
from the given collection of rules, select those with the given scope.
args:
rules (List[capa.rules.Rule]):
scope (str): one of the capa.rules.*_SCOPE constants.
returns:
List[capa.rules.Rule]:
"""
return list(rule for rule in rules if rule.scope == scope)
def get_rules_and_dependencies(rules, rule_name):
"""
from the given collection of rules, select a rule and its dependencies (transitively).
args:
rules (List[Rule]):
rule_name (str):
yields:
Rule:
"""
# we evaluate `rules` multiple times, so if its a generator, realize it into a list.
rules = list(rules)
namespaces = index_rules_by_namespace(rules)
rules = {rule.name: rule for rule in rules}
wanted = set([rule_name])
def rec(rule):
wanted.add(rule.name)
for dep in rule.get_dependencies(namespaces):
rec(rules[dep])
rec(rules[rule_name])
for rule in rules.values():
if rule.name in wanted:
yield rule
def ensure_rules_are_unique(rules):
seen = set([])
for rule in rules:
if rule.name in seen:
raise InvalidRule("duplicate rule name: " + rule.name)
seen.add(rule.name)
def ensure_rule_dependencies_are_met(rules):
"""
raise an exception if a rule dependency does not exist.
raises:
InvalidRule: if a dependency is not met.
"""
# we evaluate `rules` multiple times, so if its a generator, realize it into a list.
rules = list(rules)
namespaces = index_rules_by_namespace(rules)
rules = {rule.name: rule for rule in rules}
for rule in rules.values():
for dep in rule.get_dependencies(namespaces):
if dep not in rules:
raise InvalidRule('rule "%s" depends on missing rule "%s"' % (rule.name, dep))
def index_rules_by_namespace(rules):
"""
compute the rules that fit into each namespace found within the given rules.
for example, given:
- c2/shell :: create reverse shell
- c2/file-transfer :: download and write a file
return the index:
c2/shell: [create reverse shell]
c2/file-transfer: [download and write a file]
c2: [create reverse shell, download and write a file]
Args:
rules (List[Rule]):
Returns: Dict[str, List[Rule]]
"""
namespaces = collections.defaultdict(list)
for rule in rules:
namespace = rule.meta.get("namespace")
if not namespace:
continue
while namespace:
namespaces[namespace].append(rule)
namespace, _, _ = namespace.rpartition("/")
return dict(namespaces)
class RuleSet(object):
"""
a ruleset is initialized with a collection of rules, which it verifies and sorts into scopes.
each set of scoped rules is sorted topologically, which enables rules to match on past rule matches.
example:
ruleset = RuleSet([
Rule(...),
Rule(...),
...
])
capa.engine.match(ruleset.file_rules, ...)
"""
def __init__(self, rules):
super(RuleSet, self).__init__()
ensure_rules_are_unique(rules)
rules = self._extract_subscope_rules(rules)
ensure_rule_dependencies_are_met(rules)
if len(rules) == 0:
raise InvalidRuleSet("no rules selected")
self.file_rules = self._get_rules_for_scope(rules, FILE_SCOPE)
self.function_rules = self._get_rules_for_scope(rules, FUNCTION_SCOPE)
self.basic_block_rules = self._get_rules_for_scope(rules, BASIC_BLOCK_SCOPE)
self.rules = {rule.name: rule for rule in rules}
def __len__(self):
return len(self.rules)
def __getitem__(self, rulename):
return self.rules[rulename]
@staticmethod
def _get_rules_for_scope(rules, scope):
"""
given a collection of rules, collect the rules that are needed at the given scope.
these rules are ordered topologically.
don't include "lib" rules, unless they are dependencies of other rules.
"""
scope_rules = set([])
# we need to process all rules, not just rules with the given scope.
# this is because rules with a higher scope, e.g. file scope, may have subscope rules
# at lower scope, e.g. function scope.
# so, we find all dependencies of all rules, and later will filter them down.
for rule in rules:
if rule.meta.get("lib", False):
continue
scope_rules.update(get_rules_and_dependencies(rules, rule.name))
return get_rules_with_scope(capa.engine.topologically_order_rules(scope_rules), scope)
@staticmethod
def _extract_subscope_rules(rules):
"""
process the given sequence of rules.
for each one, extract any embedded subscope rules into their own rule.
process these recursively.
then return a list of the refactored rules.
note: this operation mutates the rules passed in - they may now have `match` statements
for the extracted subscope rules.
"""
done = []
# use a queue of rules, because we'll be modifying the list (appending new items) as we go.
while rules:
rule = rules.pop(0)
for subscope_rule in rule.extract_subscope_rules():
rules.append(subscope_rule)
done.append(rule)
return done
def filter_rules_by_meta(self, tag):
"""
return new rule set with rules filtered based on all meta field values, adds all dependency rules
apply tag-based rule filter assuming that all required rules are loaded
can be used to specify selected rules vs. providing a rules child directory where capa cannot resolve
dependencies from unknown paths
TODO handle circular dependencies?
TODO support -t=metafield <k>
"""
rules = self.rules.values()
rules_filtered = set([])
for rule in rules:
for k, v in rule.meta.items():
if isinstance(v, six.string_types) and tag in v:
logger.debug('using rule "%s" and dependencies, found tag in meta.%s: %s', rule.name, k, v)
rules_filtered.update(set(capa.rules.get_rules_and_dependencies(rules, rule.name)))
break
return RuleSet(list(rules_filtered))

1412
capa/rules/__init__.py Normal file

File diff suppressed because it is too large Load Diff

155
capa/rules/cache.py Normal file
View File

@@ -0,0 +1,155 @@
import sys
import zlib
import pickle
import hashlib
import logging
import os.path
from typing import List, Optional
from dataclasses import dataclass
import capa.rules
import capa.helpers
import capa.version
logger = logging.getLogger(__name__)
# TypeAlias. note: using `foo: TypeAlias = bar` is Python 3.10+
CacheIdentifier = str
def compute_cache_identifier(rule_content: List[bytes]) -> CacheIdentifier:
hash = hashlib.sha256()
# note that this changes with each release,
# so cache identifiers will never collide across releases.
version = capa.version.__version__
hash.update(version.encode("utf-8"))
hash.update(b"\x00")
rule_hashes = list(sorted([hashlib.sha256(buf).hexdigest() for buf in rule_content]))
for rule_hash in rule_hashes:
hash.update(rule_hash.encode("ascii"))
hash.update(b"\x00")
return hash.hexdigest()
def get_default_cache_directory() -> str:
# ref: https://github.com/mandiant/capa/issues/1212#issuecomment-1361259813
#
# Linux: $XDG_CACHE_HOME/capa/
# Windows: %LOCALAPPDATA%\flare\capa\cache
# MacOS: ~/Library/Caches/capa
# ref: https://stackoverflow.com/a/8220141/87207
if sys.platform == "linux" or sys.platform == "linux2":
directory = os.environ.get("XDG_CACHE_HOME", os.path.join(os.environ["HOME"], ".cache", "capa"))
elif sys.platform == "darwin":
directory = os.path.join(os.environ["HOME"], "Library", "Caches", "capa")
elif sys.platform == "win32":
directory = os.path.join(os.environ["LOCALAPPDATA"], "flare", "capa", "cache")
else:
raise NotImplementedError(f"unsupported platform: {sys.platform}")
os.makedirs(directory, exist_ok=True)
return directory
def get_cache_path(cache_dir: str, id: CacheIdentifier) -> str:
filename = "capa-" + id[:8] + ".cache"
return os.path.join(cache_dir, filename)
MAGIC = b"capa"
VERSION = b"\x00\x00\x00\x01"
@dataclass
class RuleCache:
id: CacheIdentifier
ruleset: capa.rules.RuleSet
def dump(self):
return MAGIC + VERSION + self.id.encode("ascii") + zlib.compress(pickle.dumps(self))
@staticmethod
def load(data):
assert data.startswith(MAGIC + VERSION)
id = data[0x8:0x48].decode("ascii")
cache = pickle.loads(zlib.decompress(data[0x48:]))
assert isinstance(cache, RuleCache)
assert cache.id == id
return cache
def get_ruleset_content(ruleset: capa.rules.RuleSet) -> List[bytes]:
rule_contents = []
for rule in ruleset.rules.values():
if rule.is_subscope_rule():
continue
rule_contents.append(rule.definition.encode("utf-8"))
return rule_contents
def compute_ruleset_cache_identifier(ruleset: capa.rules.RuleSet) -> CacheIdentifier:
rule_contents = get_ruleset_content(ruleset)
return compute_cache_identifier(rule_contents)
def cache_ruleset(cache_dir: str, ruleset: capa.rules.RuleSet):
"""
cache the given ruleset to disk, using the given cache directory.
this can subsequently be reloaded via `load_cached_ruleset`,
assuming the capa version and rule content does not change.
callers should use this function to avoid the performance overhead
of validating rules on each run.
"""
id = compute_ruleset_cache_identifier(ruleset)
path = get_cache_path(cache_dir, id)
if os.path.exists(path):
logger.debug("rule set already cached to %s", path)
return
cache = RuleCache(id, ruleset)
with open(path, "wb") as f:
f.write(cache.dump())
logger.debug("rule set cached to %s", path)
return
def load_cached_ruleset(cache_dir: str, rule_contents: List[bytes]) -> Optional[capa.rules.RuleSet]:
"""
load a cached ruleset from disk, using the given cache directory.
the raw rule contents are required here to prove that the rules haven't changed
and to avoid stale cache entries.
callers should use this function to avoid the performance overhead
of validating rules on each run.
"""
id = compute_cache_identifier(rule_contents)
path = get_cache_path(cache_dir, id)
if not os.path.exists(path):
logger.debug("rule set cache does not exist: %s", path)
return None
logger.debug("loading rule set from cache: %s", path)
with open(path, "rb") as f:
buf = f.read()
try:
cache = RuleCache.load(buf)
except AssertionError:
logger.debug("rule set cache is invalid: %s", path)
# delete the cache that seems to be invalid.
os.remove(path)
return None
else:
return cache.ruleset

View File

@@ -1 +1,5 @@
__version__ = "1.3.0"
__version__ = "5.0.0"
def get_major_version():
return int(__version__.partition(".")[0])

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