From e8cef536f63b24d89d41b95daddc4beb70142dbc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Feb 2023 14:59:26 +0000 Subject: [PATCH 01/49] build(deps-dev): bump pyinstaller from 5.7.0 to 5.8.0 Bumps [pyinstaller](https://github.com/pyinstaller/pyinstaller) from 5.7.0 to 5.8.0. - [Release notes](https://github.com/pyinstaller/pyinstaller/releases) - [Changelog](https://github.com/pyinstaller/pyinstaller/blob/develop/doc/CHANGES.rst) - [Commits](https://github.com/pyinstaller/pyinstaller/compare/v5.7.0...v5.8.0) --- updated-dependencies: - dependency-name: pyinstaller dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 5fc6f8d5..f7f55b33 100644 --- a/setup.py +++ b/setup.py @@ -89,7 +89,7 @@ setuptools.setup( "types_requests==2.28.1", ], "build": [ - "pyinstaller==5.7.0", + "pyinstaller==5.8.0", ], }, zip_safe=False, From 00ecfe7a8071ebc9465e0a5e9cee10a7f00f5265 Mon Sep 17 00:00:00 2001 From: Capa Bot Date: Wed, 15 Feb 2023 10:22:12 +0000 Subject: [PATCH 02/49] Sync capa-testfiles submodule --- tests/data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/data b/tests/data index a866c54c..a4c0fdd2 160000 --- a/tests/data +++ b/tests/data @@ -1 +1 @@ -Subproject commit a866c54c0fc117be94aa7d8b563197a2823d01ca +Subproject commit a4c0fdd2ec85c005e78000e110139f1da1b76355 From 0d14c168a40fd47c709f3e2cd409d05392731eab Mon Sep 17 00:00:00 2001 From: mr-tz Date: Wed, 15 Feb 2023 11:39:37 +0100 Subject: [PATCH 03/49] fix loop detection corner case --- CHANGELOG.md | 1 + capa/features/extractors/viv/function.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 300dd9c5..d0b16120 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - ### Bug Fixes +- extractor: fix vivisect loop detection corner case #1310 @mr-tz ### capa explorer IDA Pro plugin diff --git a/capa/features/extractors/viv/function.py b/capa/features/extractors/viv/function.py index cf1df527..50d5792e 100644 --- a/capa/features/extractors/viv/function.py +++ b/capa/features/extractors/viv/function.py @@ -47,6 +47,10 @@ def extract_function_loop(fhandle: FunctionHandle) -> Iterator[Tuple[Feature, Ad for bb in f.basic_blocks: if len(bb.instructions) > 0: for bva, bflags in bb.instructions[-1].getBranches(): + if bva is None: + # vivisect may be unable to recover the call target, e.g. on dynamic calls like `call esi` + # for this bva is None, and we don't want to add it for loop detection, ref: vivisect#574 + continue # vivisect does not set branch flags for non-conditional jmp so add explicit check if ( bflags & envi.BR_COND From 53475c9643f8ea9ec4ae8b118bfde1a77ca27478 Mon Sep 17 00:00:00 2001 From: Capa Bot Date: Wed, 15 Feb 2023 10:55:49 +0000 Subject: [PATCH 04/49] Sync capa rules submodule --- rules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules b/rules index 2586016c..9cda9949 160000 --- a/rules +++ b/rules @@ -1 +1 @@ -Subproject commit 2586016cf2fb92298fb6ac462d88c12c91b76e73 +Subproject commit 9cda994949c02f2e20a8db1d61fd36d1a45fe476 From eac3d8336dc12def470938d6711284a725404250 Mon Sep 17 00:00:00 2001 From: Capa Bot Date: Wed, 15 Feb 2023 10:56:23 +0000 Subject: [PATCH 05/49] Sync capa-testfiles submodule --- tests/data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/data b/tests/data index a4c0fdd2..c7f374f5 160000 --- a/tests/data +++ b/tests/data @@ -1 +1 @@ -Subproject commit a4c0fdd2ec85c005e78000e110139f1da1b76355 +Subproject commit c7f374f5250a821727f4547f96cbe278307c4e65 From 4ccf6f0e69a35edf83f8d93a0e698009282daee4 Mon Sep 17 00:00:00 2001 From: Capa Bot Date: Wed, 15 Feb 2023 10:57:23 +0000 Subject: [PATCH 06/49] Sync capa rules submodule --- CHANGELOG.md | 3 ++- README.md | 2 +- rules | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0b16120..1d899162 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,11 +6,12 @@ ### Breaking Changes -### New Rules (3) +### New Rules (4) - persistence/scheduled-tasks/schedule-task-via-at joren485 - data-manipulation/prng/generate-random-numbers-via-rtlgenrandom william.ballenthin@mandiant.com - communication/ip/convert-ip-address-from-string @mr-tz +- data-manipulation/compression/compress-data-via-zlib-inflate-or-deflate blas.kojusner@mandiant.com - ### Bug Fixes diff --git a/README.md b/README.md index 64bd071b..6eeac537 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/flare-capa)](https://pypi.org/project/flare-capa) [![Last release](https://img.shields.io/github/v/release/mandiant/capa)](https://github.com/mandiant/capa/releases) -[![Number of rules](https://img.shields.io/badge/rules-773-blue.svg)](https://github.com/mandiant/capa-rules) +[![Number of rules](https://img.shields.io/badge/rules-774-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) diff --git a/rules b/rules index 9cda9949..8409d7d3 160000 --- a/rules +++ b/rules @@ -1 +1 @@ -Subproject commit 9cda994949c02f2e20a8db1d61fd36d1a45fe476 +Subproject commit 8409d7d33dfd511b61e952ed895404941cd13caf From 4b472c85644efefd8b463c8b450d635d30014c5f Mon Sep 17 00:00:00 2001 From: Capa Bot Date: Wed, 15 Feb 2023 15:16:41 +0000 Subject: [PATCH 07/49] Sync capa rules submodule --- CHANGELOG.md | 3 ++- README.md | 2 +- rules | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d899162..b6fb9f4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,12 +6,13 @@ ### Breaking Changes -### New Rules (4) +### New Rules (5) - persistence/scheduled-tasks/schedule-task-via-at joren485 - data-manipulation/prng/generate-random-numbers-via-rtlgenrandom william.ballenthin@mandiant.com - communication/ip/convert-ip-address-from-string @mr-tz - data-manipulation/compression/compress-data-via-zlib-inflate-or-deflate blas.kojusner@mandiant.com +- executable/installer/dotnet/packaged-as-single-file-dotnet-application michael.hunhoff@mandiant.com - ### Bug Fixes diff --git a/README.md b/README.md index 6eeac537..e54f0912 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![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-774-blue.svg)](https://github.com/mandiant/capa-rules) +[![Number of rules](https://img.shields.io/badge/rules-775-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) diff --git a/rules b/rules index 8409d7d3..54a16938 160000 --- a/rules +++ b/rules @@ -1 +1 @@ -Subproject commit 8409d7d33dfd511b61e952ed895404941cd13caf +Subproject commit 54a169386d13280d1908831b7f16b7223ac90a39 From 6e5302e5ecd17dfb59b261080bb559a443bc97dd Mon Sep 17 00:00:00 2001 From: Capa Bot Date: Wed, 15 Feb 2023 16:46:14 +0000 Subject: [PATCH 08/49] Sync capa rules submodule --- CHANGELOG.md | 3 ++- README.md | 2 +- rules | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b6fb9f4d..94415333 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,13 +6,14 @@ ### Breaking Changes -### New Rules (5) +### New Rules (6) - persistence/scheduled-tasks/schedule-task-via-at joren485 - data-manipulation/prng/generate-random-numbers-via-rtlgenrandom william.ballenthin@mandiant.com - communication/ip/convert-ip-address-from-string @mr-tz - data-manipulation/compression/compress-data-via-zlib-inflate-or-deflate blas.kojusner@mandiant.com - executable/installer/dotnet/packaged-as-single-file-dotnet-application michael.hunhoff@mandiant.com +- communication/socket/create-raw-socket blas.kojusner@mandiant.com - ### Bug Fixes diff --git a/README.md b/README.md index e54f0912..c78a7ea3 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![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-775-blue.svg)](https://github.com/mandiant/capa-rules) +[![Number of rules](https://img.shields.io/badge/rules-776-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) diff --git a/rules b/rules index 54a16938..b035bb8d 160000 --- a/rules +++ b/rules @@ -1 +1 @@ -Subproject commit 54a169386d13280d1908831b7f16b7223ac90a39 +Subproject commit b035bb8d90e556f412b6ee9ef738ce6c68bbd9cd From b9edb6dbc918d2905a6477aadee67abc59f490d3 Mon Sep 17 00:00:00 2001 From: Capa Bot Date: Thu, 16 Feb 2023 10:31:51 +0000 Subject: [PATCH 09/49] Sync capa-testfiles submodule --- tests/data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/data b/tests/data index c7f374f5..d19468ce 160000 --- a/tests/data +++ b/tests/data @@ -1 +1 @@ -Subproject commit c7f374f5250a821727f4547f96cbe278307c4e65 +Subproject commit d19468ce08c1f887626971f6ff92b9ad28c32360 From 5e600d02a88cbf2f0fbfc60ac14f9ccf263c3ada Mon Sep 17 00:00:00 2001 From: Capa Bot Date: Mon, 20 Feb 2023 08:05:09 +0000 Subject: [PATCH 10/49] Sync capa rules submodule --- CHANGELOG.md | 3 ++- README.md | 2 +- rules | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94415333..f4b302a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ ### Breaking Changes -### New Rules (6) +### New Rules (7) - persistence/scheduled-tasks/schedule-task-via-at joren485 - data-manipulation/prng/generate-random-numbers-via-rtlgenrandom william.ballenthin@mandiant.com @@ -14,6 +14,7 @@ - data-manipulation/compression/compress-data-via-zlib-inflate-or-deflate blas.kojusner@mandiant.com - executable/installer/dotnet/packaged-as-single-file-dotnet-application michael.hunhoff@mandiant.com - communication/socket/create-raw-socket blas.kojusner@mandiant.com +- communication/http/reference-http-user-agent-string @mr-tz - ### Bug Fixes diff --git a/README.md b/README.md index c78a7ea3..0d9c510e 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![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-776-blue.svg)](https://github.com/mandiant/capa-rules) +[![Number of rules](https://img.shields.io/badge/rules-777-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) diff --git a/rules b/rules index b035bb8d..7395c8ef 160000 --- a/rules +++ b/rules @@ -1 +1 @@ -Subproject commit b035bb8d90e556f412b6ee9ef738ce6c68bbd9cd +Subproject commit 7395c8efe08d8ad2d5a60adb1baabf4b821c8dff From c681175685d24057a519e33b14f3528b4390d8f2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Feb 2023 14:58:27 +0000 Subject: [PATCH 11/49] build(deps): bump pydantic from 1.10.4 to 1.10.5 Bumps [pydantic](https://github.com/pydantic/pydantic) from 1.10.4 to 1.10.5. - [Release notes](https://github.com/pydantic/pydantic/releases) - [Changelog](https://github.com/pydantic/pydantic/blob/v1.10.5/HISTORY.md) - [Commits](https://github.com/pydantic/pydantic/compare/v1.10.4...v1.10.5) --- updated-dependencies: - dependency-name: pydantic dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 5fc6f8d5..5c742555 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ requirements = [ "pyelftools==0.29", "dnfile==0.13.0", "dncil==1.0.2", - "pydantic==1.10.4", + "pydantic==1.10.5", ] # this sets __version__ From b6ac6d295977c8a8af713f6b7d7432be7d6bf943 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Feb 2023 14:58:39 +0000 Subject: [PATCH 12/49] build(deps-dev): bump mypy from 0.991 to 1.0.1 Bumps [mypy](https://github.com/python/mypy) from 0.991 to 1.0.1. - [Release notes](https://github.com/python/mypy/releases) - [Commits](https://github.com/python/mypy/compare/v0.991...v1.0.1) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 5fc6f8d5..b855c3ae 100644 --- a/setup.py +++ b/setup.py @@ -75,7 +75,7 @@ setuptools.setup( "pycodestyle==2.10.0", "black==23.1.0", "isort==5.11.4", - "mypy==0.991", + "mypy==1.0.1", "psutil==5.9.2", "stix2==3.0.1", "requests==2.28.0", From 02f8e57e66bce6a67a8cf1554b0754f7ac4aaa3c Mon Sep 17 00:00:00 2001 From: Capa Bot Date: Tue, 21 Feb 2023 10:46:20 +0000 Subject: [PATCH 13/49] Sync capa rules submodule --- CHANGELOG.md | 3 ++- README.md | 2 +- rules | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4b302a8..4ae09f0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ ### Breaking Changes -### New Rules (7) +### New Rules (8) - persistence/scheduled-tasks/schedule-task-via-at joren485 - data-manipulation/prng/generate-random-numbers-via-rtlgenrandom william.ballenthin@mandiant.com @@ -15,6 +15,7 @@ - executable/installer/dotnet/packaged-as-single-file-dotnet-application michael.hunhoff@mandiant.com - communication/socket/create-raw-socket blas.kojusner@mandiant.com - communication/http/reference-http-user-agent-string @mr-tz +- communication/http/get-http-content-length william.ballenthin@mandiant.com - ### Bug Fixes diff --git a/README.md b/README.md index 0d9c510e..aa0c9c20 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![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-777-blue.svg)](https://github.com/mandiant/capa-rules) +[![Number of rules](https://img.shields.io/badge/rules-778-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) diff --git a/rules b/rules index 7395c8ef..5741fdd7 160000 --- a/rules +++ b/rules @@ -1 +1 @@ -Subproject commit 7395c8efe08d8ad2d5a60adb1baabf4b821c8dff +Subproject commit 5741fdd7f34b3871178c4c15b84d2c7925948e39 From 599d3ac92cd988a976e5c33384197295c9e2f1dd Mon Sep 17 00:00:00 2001 From: Capa Bot Date: Tue, 21 Feb 2023 21:38:32 +0000 Subject: [PATCH 14/49] Sync capa rules submodule --- rules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules b/rules index 5741fdd7..e5ae5056 160000 --- a/rules +++ b/rules @@ -1 +1 @@ -Subproject commit 5741fdd7f34b3871178c4c15b84d2c7925948e39 +Subproject commit e5ae5056828c675601848b050a496f7466fab261 From 7a1f2f4b3b7b09951c6900d0f46b09950c91dff0 Mon Sep 17 00:00:00 2001 From: Capa Bot Date: Wed, 22 Feb 2023 19:24:48 +0000 Subject: [PATCH 15/49] Sync capa rules submodule --- rules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules b/rules index e5ae5056..c9f7f685 160000 --- a/rules +++ b/rules @@ -1 +1 @@ -Subproject commit e5ae5056828c675601848b050a496f7466fab261 +Subproject commit c9f7f685dde7ab9f452794ac9c2d8c65697825ac From 17f70bb87caf7238f4c88d5d8445c24381c34353 Mon Sep 17 00:00:00 2001 From: Capa Bot Date: Thu, 23 Feb 2023 08:47:24 +0000 Subject: [PATCH 16/49] Sync capa rules submodule --- CHANGELOG.md | 3 ++- README.md | 2 +- rules | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ae09f0b..ffb42ff6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ ### Breaking Changes -### New Rules (8) +### New Rules (9) - persistence/scheduled-tasks/schedule-task-via-at joren485 - data-manipulation/prng/generate-random-numbers-via-rtlgenrandom william.ballenthin@mandiant.com @@ -16,6 +16,7 @@ - communication/socket/create-raw-socket blas.kojusner@mandiant.com - communication/http/reference-http-user-agent-string @mr-tz - communication/http/get-http-content-length william.ballenthin@mandiant.com +- nursery/move-directory michael.hunhoff@mandiant.com - ### Bug Fixes diff --git a/README.md b/README.md index aa0c9c20..e9ec8571 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![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-778-blue.svg)](https://github.com/mandiant/capa-rules) +[![Number of rules](https://img.shields.io/badge/rules-779-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) diff --git a/rules b/rules index c9f7f685..ca0d7ad8 160000 --- a/rules +++ b/rules @@ -1 +1 @@ -Subproject commit c9f7f685dde7ab9f452794ac9c2d8c65697825ac +Subproject commit ca0d7ad8553cf00d9882f9835e08bc734a8b23d4 From a07ca443f0c04994cebea651c2d583109f6d0ce0 Mon Sep 17 00:00:00 2001 From: Mike Hunhoff Date: Fri, 24 Feb 2023 07:51:40 -0700 Subject: [PATCH 17/49] update OS to match OS_ANY for all supported OSes (#1324) --- CHANGELOG.md | 1 + capa/features/common.py | 14 +++++++++++ tests/test_match.py | 51 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 65 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ffb42ff6..263b0477 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ ### Bug Fixes - extractor: fix vivisect loop detection corner case #1310 @mr-tz +- match: extend OS characteristic to match OS_ANY to all supported OSes #1324 @mike-hunhoff ### capa explorer IDA Pro plugin diff --git a/capa/features/common.py b/capa/features/common.py index cf2c02f3..c908d7bc 100644 --- a/capa/features/common.py +++ b/capa/features/common.py @@ -428,6 +428,20 @@ class OS(Feature): super().__init__(value, description=description) self.name = "os" + def evaluate(self, ctx, **kwargs): + capa.perf.counters["evaluate.feature"] += 1 + capa.perf.counters["evaluate.feature." + self.name] += 1 + + for feature, locations in ctx.items(): + if not isinstance(feature, (OS,)): + continue + + assert isinstance(feature.value, str) + if OS_ANY in (self.value, feature.value) or self.value == feature.value: + return Result(True, self, [], locations=locations) + + return Result(False, self, []) + FORMAT_PE = "pe" FORMAT_ELF = "elf" diff --git a/tests/test_match.py b/tests/test_match.py index 2d8b9f2a..510db98f 100644 --- a/tests/test_match.py +++ b/tests/test_match.py @@ -13,7 +13,6 @@ import capa.engine import capa.features.insn import capa.features.common from capa.rules import Scope -from capa.features import * from capa.features.insn import * from capa.features.common import * @@ -626,3 +625,53 @@ def test_match_property_access(): 0x0, ) assert "test rule" not in matches + + +def test_match_os_any(): + rule = textwrap.dedent( + """ + rule: + meta: + name: test rule + features: + - or: + - and: + - or: + - os: windows + - os: linux + - os: macos + - string: "Hello world" + - and: + - os: any + - string: "Goodbye world" + """ + ) + r = capa.rules.Rule.from_yaml(rule) + + _, matches = match( + [r], + {OS(OS_ANY): {1}, String("Hello world"): {1}}, + 0x0, + ) + assert "test rule" in matches + + _, matches = match( + [r], + {OS(OS_WINDOWS): {1}, String("Hello world"): {1}}, + 0x0, + ) + assert "test rule" in matches + + _, matches = match( + [r], + {OS(OS_ANY): {1}, String("Goodbye world"): {1}}, + 0x0, + ) + assert "test rule" in matches + + _, matches = match( + [r], + {OS(OS_WINDOWS): {1}, String("Goodbye world"): {1}}, + 0x0, + ) + assert "test rule" in matches From d663007e601d6ba6458d5c73f66a85f99ef4fdce Mon Sep 17 00:00:00 2001 From: Capa Bot Date: Fri, 24 Feb 2023 14:52:58 +0000 Subject: [PATCH 18/49] Sync capa rules submodule --- rules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules b/rules index ca0d7ad8..64e524f9 160000 --- a/rules +++ b/rules @@ -1 +1 @@ -Subproject commit ca0d7ad8553cf00d9882f9835e08bc734a8b23d4 +Subproject commit 64e524f9d6520f88387f1dd54ee596a168060346 From 501e213dcec2e062648d282852fed0b6717915c6 Mon Sep 17 00:00:00 2001 From: Capa Bot Date: Mon, 27 Feb 2023 08:59:54 +0000 Subject: [PATCH 19/49] Sync capa rules submodule --- rules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules b/rules index 64e524f9..abf75509 160000 --- a/rules +++ b/rules @@ -1 +1 @@ -Subproject commit 64e524f9d6520f88387f1dd54ee596a168060346 +Subproject commit abf75509fa4bf36d0a84952586c3cb2b596b597b From 199a5cff4b318a944df404dbf6eab36d82611722 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Feb 2023 14:59:14 +0000 Subject: [PATCH 20/49] build(deps-dev): bump types-tabulate from 0.9.0.0 to 0.9.0.1 Bumps [types-tabulate](https://github.com/python/typeshed) from 0.9.0.0 to 0.9.0.1. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-tabulate dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index abbb1a77..c39f4c8b 100644 --- a/setup.py +++ b/setup.py @@ -83,7 +83,7 @@ setuptools.setup( "types-backports==0.1.3", "types-colorama==0.4.15", "types-PyYAML==6.0.8", - "types-tabulate==0.9.0.0", + "types-tabulate==0.9.0.1", "types-termcolor==1.1.4", "types-psutil==5.8.23", "types_requests==2.28.1", From bd7cf8cdd1c6c2f9f4abd93e1b38e378ee07e3ba Mon Sep 17 00:00:00 2001 From: Capa Bot Date: Tue, 28 Feb 2023 10:41:07 +0000 Subject: [PATCH 21/49] Sync capa rules submodule --- CHANGELOG.md | 3 ++- README.md | 2 +- rules | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 263b0477..a2fa3d59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ ### Breaking Changes -### New Rules (9) +### New Rules (10) - persistence/scheduled-tasks/schedule-task-via-at joren485 - data-manipulation/prng/generate-random-numbers-via-rtlgenrandom william.ballenthin@mandiant.com @@ -17,6 +17,7 @@ - communication/http/reference-http-user-agent-string @mr-tz - communication/http/get-http-content-length william.ballenthin@mandiant.com - nursery/move-directory michael.hunhoff@mandiant.com +- nursery/get-http-request-uri william.ballenthin@mandiant.com - ### Bug Fixes diff --git a/README.md b/README.md index e9ec8571..0420e567 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![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-779-blue.svg)](https://github.com/mandiant/capa-rules) +[![Number of rules](https://img.shields.io/badge/rules-780-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) diff --git a/rules b/rules index abf75509..1bc2fe6a 160000 --- a/rules +++ b/rules @@ -1 +1 @@ -Subproject commit abf75509fa4bf36d0a84952586c3cb2b596b597b +Subproject commit 1bc2fe6a7d426d17f5b1a96b0907dbff7c342071 From f7371c4a9f737bee03bb03127e8b95f380c93a7e Mon Sep 17 00:00:00 2001 From: Capa Bot Date: Wed, 1 Mar 2023 15:09:07 +0000 Subject: [PATCH 22/49] Sync capa rules submodule --- rules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules b/rules index 1bc2fe6a..f81d0472 160000 --- a/rules +++ b/rules @@ -1 +1 @@ -Subproject commit 1bc2fe6a7d426d17f5b1a96b0907dbff7c342071 +Subproject commit f81d04729adfaa1fa214b72fb35b3fc982a759e4 From be6bb879f372a5f50f414a1f005a7545532347ae Mon Sep 17 00:00:00 2001 From: Capa Bot Date: Wed, 1 Mar 2023 15:50:20 +0000 Subject: [PATCH 23/49] Sync capa rules submodule --- CHANGELOG.md | 4 +++- README.md | 2 +- rules | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a2fa3d59..9233666a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ ### Breaking Changes -### New Rules (10) +### New Rules (12) - persistence/scheduled-tasks/schedule-task-via-at joren485 - data-manipulation/prng/generate-random-numbers-via-rtlgenrandom william.ballenthin@mandiant.com @@ -18,6 +18,8 @@ - communication/http/get-http-content-length william.ballenthin@mandiant.com - nursery/move-directory michael.hunhoff@mandiant.com - nursery/get-http-request-uri william.ballenthin@mandiant.com +- nursery/create-zip-archive-in-dotnet michael.hunhoff@mandiant.com +- nursery/extract-zip-archive-in-dotnet anushka.virgaonkar@mandiant.com michael.hunhoff@mandiant.com - ### Bug Fixes diff --git a/README.md b/README.md index 0420e567..cdf94867 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![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-780-blue.svg)](https://github.com/mandiant/capa-rules) +[![Number of rules](https://img.shields.io/badge/rules-781-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) diff --git a/rules b/rules index f81d0472..5351554f 160000 --- a/rules +++ b/rules @@ -1 +1 @@ -Subproject commit f81d04729adfaa1fa214b72fb35b3fc982a759e4 +Subproject commit 5351554ff9a1b379161682054847125e15cc5efb From 52de09a0325c064a2259759772d5fdccd22f14ff Mon Sep 17 00:00:00 2001 From: Moritz Date: Thu, 2 Mar 2023 10:33:14 +0100 Subject: [PATCH 24/49] Fix byte/string extraction and unit tests (#1339) * Fix wrong expected results on string and bytes tests. Fix https://github.com/mandiant/capa/issues/1336 * Fix IDA insn/byte extractor checks wrong address. Fix https://github.com/mandiant/capa/issues/1327 * fix vivisect string check and tests --------- Co-authored-by: Xusheng --- CHANGELOG.md | 1 + capa/features/extractors/ida/insn.py | 2 +- capa/features/extractors/viv/insn.py | 7 ++++++- tests/fixtures.py | 18 +++++++++++++----- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9233666a..443de56b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ ### Bug Fixes - extractor: fix vivisect loop detection corner case #1310 @mr-tz - match: extend OS characteristic to match OS_ANY to all supported OSes #1324 @mike-hunhoff +- extractor: fix IDA and vivisect string and bytes features overlap and tests #1327 #1336 @xusheng6 ### capa explorer IDA Pro plugin diff --git a/capa/features/extractors/ida/insn.py b/capa/features/extractors/ida/insn.py index 9c579691..ac8c8956 100644 --- a/capa/features/extractors/ida/insn.py +++ b/capa/features/extractors/ida/insn.py @@ -172,7 +172,7 @@ def extract_insn_bytes_features(fh: FunctionHandle, bbh: BBHandle, ih: InsnHandl if ref != insn.ea: extracted_bytes = capa.features.extractors.ida.helpers.read_bytes_at(ref, MAX_BYTES_FEATURE_SIZE) if extracted_bytes and not capa.features.extractors.helpers.all_zeros(extracted_bytes): - if not capa.features.extractors.ida.helpers.find_string_at(insn.ea): + if not capa.features.extractors.ida.helpers.find_string_at(ref): # don't extract byte features for obvious strings yield Bytes(extracted_bytes), ih.address diff --git a/capa/features/extractors/viv/insn.py b/capa/features/extractors/viv/insn.py index 1ef954c3..d324f31e 100644 --- a/capa/features/extractors/viv/insn.py +++ b/capa/features/extractors/viv/insn.py @@ -175,8 +175,13 @@ def derefs(vw, p): while True: if not vw.isValidPointer(p): return + yield p + if vw.isProbablyString(p) or vw.isProbablyUnicode(p): + # don't deref strings that coincidentally are pointers + return + try: next = vw.readMemoryPtr(p) except Exception: @@ -271,7 +276,7 @@ def extract_insn_bytes_features(fh: FunctionHandle, bb, ih: InsnHandle) -> Itera if capa.features.extractors.helpers.all_zeros(buf): continue - if f.vw.isProbablyString(v): + if f.vw.isProbablyString(v) or f.vw.isProbablyUnicode(v): # don't extract byte features for obvious strings continue diff --git a/tests/fixtures.py b/tests/fixtures.py index ae336f56..1c4d2ad3 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -337,6 +337,9 @@ def get_sample_md5_by_name(name): return "946a99f36a46d335dec080d9a4371940" elif name.startswith("b9f5b"): return "b9f5bd514485fb06da39beff051b9fdc" + elif name.startswith("294b8d"): + # file name is SHA256 hash + return "3db3e55b16a7b1b1afb970d5e77c5d98" else: raise ValueError("unexpected sample fixture: %s" % name) @@ -643,14 +646,19 @@ FEATURE_PRESENCE_TESTS = sorted( # insn/string, direct memory reference ("mimikatz", "function=0x46D6CE", capa.features.common.String("(null)"), True), # insn/bytes - ("mimikatz", "function=0x40105D", capa.features.common.Bytes("SCardControl".encode("utf-16le")), True), - ("mimikatz", "function=0x40105D", capa.features.common.Bytes("SCardTransmit".encode("utf-16le")), True), - ("mimikatz", "function=0x40105D", capa.features.common.Bytes("ACR > ".encode("utf-16le")), True), + ("mimikatz", "function=0x401517", capa.features.common.Bytes(binascii.unhexlify("CA3B0E000000F8AF47")), True), + ("mimikatz", "function=0x404414", capa.features.common.Bytes(binascii.unhexlify("0180000040EA4700")), True), + # don't extract byte features for obvious strings + ("mimikatz", "function=0x40105D", capa.features.common.Bytes("SCardControl".encode("utf-16le")), False), + ("mimikatz", "function=0x40105D", capa.features.common.Bytes("SCardTransmit".encode("utf-16le")), False), + ("mimikatz", "function=0x40105D", capa.features.common.Bytes("ACR > ".encode("utf-16le")), False), ("mimikatz", "function=0x40105D", capa.features.common.Bytes("nope".encode("ascii")), False), + # push offset aAcsAcr1220 ; "ACS..." -> where ACS == 41 00 43 00 == valid pointer to middle of instruction + ("mimikatz", "function=0x401000", capa.features.common.Bytes(binascii.unhexlify("FDFF59F647")), False), # IDA features included byte sequences read from invalid memory, fixed in #409 ("mimikatz", "function=0x44570F", capa.features.common.Bytes(binascii.unhexlify("FF" * 256)), False), - # insn/bytes, pointer to bytes - ("mimikatz", "function=0x44EDEF", capa.features.common.Bytes("INPUTEVENT".encode("utf-16le")), True), + # insn/bytes, pointer to string bytes + ("mimikatz", "function=0x44EDEF", capa.features.common.Bytes("INPUTEVENT".encode("utf-16le")), False), # insn/characteristic(nzxor) ("mimikatz", "function=0x410DFC", capa.features.common.Characteristic("nzxor"), True), ("mimikatz", "function=0x40105D", capa.features.common.Characteristic("nzxor"), False), From 9f3428e1c365aec7e2186f5b0fcb2994fb2df875 Mon Sep 17 00:00:00 2001 From: Mike Hunhoff Date: Thu, 2 Mar 2023 13:42:43 -0700 Subject: [PATCH 25/49] explorer: fix plugin exception when loaded under idat (#1341) --- CHANGELOG.md | 1 + capa/ida/plugin/__init__.py | 6 ++++++ capa/ida/plugin/form.py | 8 ++++++-- capa/ida/plugin/icon.py | 6 ------ 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 443de56b..543db9ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ ### Bug Fixes - extractor: fix vivisect loop detection corner case #1310 @mr-tz - match: extend OS characteristic to match OS_ANY to all supported OSes #1324 @mike-hunhoff +- explorer: fix exception when plugin loaded in IDA hosted under idat #1341 @mike-hunhoff - extractor: fix IDA and vivisect string and bytes features overlap and tests #1327 #1336 @xusheng6 ### capa explorer IDA Pro plugin diff --git a/capa/ida/plugin/__init__.py b/capa/ida/plugin/__init__.py index 7ccb8acf..8030a800 100644 --- a/capa/ida/plugin/__init__.py +++ b/capa/ida/plugin/__init__.py @@ -38,6 +38,12 @@ class CapaExplorerPlugin(idaapi.plugin_t): """called when IDA is loading the plugin""" logging.basicConfig(level=logging.INFO) + # do not load plugin unless hosted in idaq (IDA Qt) + if not idaapi.is_idaq(): + # note: it does not appear that IDA calls "init" by default when hosted in idat; we keep this + # check here for good measure + return idaapi.PLUGIN_SKIP + import capa.ida.helpers # do not load plugin if IDA version/file type not supported diff --git a/capa/ida/plugin/form.py b/capa/ida/plugin/form.py index 1a7a2ef1..2f30928d 100644 --- a/capa/ida/plugin/form.py +++ b/capa/ida/plugin/form.py @@ -29,7 +29,7 @@ import capa.features.extractors.ida.extractor from capa.rules import Rule from capa.engine import FeatureSet from capa.rules.cache import compute_ruleset_cache_identifier -from capa.ida.plugin.icon import QICON +from capa.ida.plugin.icon import ICON from capa.ida.plugin.view import ( CapaExplorerQtreeView, CapaExplorerRulegenEditor, @@ -238,7 +238,11 @@ class CapaExplorerForm(idaapi.PluginForm): load interface and install hooks but do not analyze database """ self.parent = self.FormToPyQtWidget(form) - self.parent.setWindowIcon(QICON) + + pixmap = QtGui.QPixmap() + pixmap.loadFromData(ICON) + + self.parent.setWindowIcon(QtGui.QIcon(pixmap)) self.load_interface() self.load_ida_hooks() diff --git a/capa/ida/plugin/icon.py b/capa/ida/plugin/icon.py index fcda46db..76a6dfb2 100644 --- a/capa/ida/plugin/icon.py +++ b/capa/ida/plugin/icon.py @@ -1,13 +1,7 @@ import base64 -from PyQt5 import QtGui - # this is just `capa/.github/icon.png`. # embed it in source so we don't have to figure out how to package into pypi release. ICON = base64.b64decode( "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" ) - -pixmap = QtGui.QPixmap() -pixmap.loadFromData(ICON) -QICON = QtGui.QIcon(pixmap) From aacfcaaa239db18b5c68986163bf38f6823fb18b Mon Sep 17 00:00:00 2001 From: Mike Hunhoff Date: Fri, 3 Mar 2023 09:52:50 -0700 Subject: [PATCH 26/49] explorer: improve embedded PE detection --- capa/features/extractors/ida/file.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/capa/features/extractors/ida/file.py b/capa/features/extractors/ida/file.py index 0d380a88..532d5a89 100644 --- a/capa/features/extractors/ida/file.py +++ b/capa/features/extractors/ida/file.py @@ -21,12 +21,14 @@ from capa.features.file import Export, Import, Section, FunctionName from capa.features.common import FORMAT_PE, FORMAT_ELF, Format, String, Feature, Characteristic from capa.features.address import NO_ADDRESS, Address, FileOffsetAddress, AbsoluteVirtualAddress +MAX_OFFSET_PE_AFTER_MZ = 0x200 + def check_segment_for_pe(seg: idaapi.segment_t) -> Iterator[Tuple[int, int]]: """check segment for embedded PE adapted for IDA from: - https://github.com/vivisect/vivisect/blob/7be4037b1cecc4551b397f840405a1fc606f9b53/PE/carve.py#L19 + https://github.com/vivisect/vivisect/blob/91e8419a861f49779f18316f155311967e696836/PE/carve.py#L25 """ seg_max = seg.end_ea mz_xor = [ @@ -40,13 +42,14 @@ def check_segment_for_pe(seg: idaapi.segment_t) -> Iterator[Tuple[int, int]]: todo = [] for mzx, pex, i in mz_xor: + # find all segment offsets containing XOR'd "MZ" bytes for off in capa.features.extractors.ida.helpers.find_byte_sequence(seg.start_ea, seg.end_ea, mzx): todo.append((off, mzx, pex, i)) while len(todo): off, mzx, pex, i = todo.pop() - # The MZ header has one field we will check e_lfanew is at 0x3c + # MZ header has one field we will check e_lfanew is at 0x3c e_lfanew = off + 0x3C if seg_max < (e_lfanew + 4): @@ -54,6 +57,10 @@ def check_segment_for_pe(seg: idaapi.segment_t) -> Iterator[Tuple[int, int]]: newoff = struct.unpack(" MAX_OFFSET_PE_AFTER_MZ: + continue + peoff = off + newoff if seg_max < (peoff + 2): continue @@ -61,9 +68,6 @@ def check_segment_for_pe(seg: idaapi.segment_t) -> Iterator[Tuple[int, int]]: if idc.get_bytes(peoff, 2) == pex: yield off, i - for nextres in capa.features.extractors.ida.helpers.find_byte_sequence(off + 1, seg.end_ea, mzx): - todo.append((nextres, mzx, pex, i)) - def extract_file_embedded_pe() -> Iterator[Tuple[Feature, Address]]: """extract embedded PE features From 14c18727db75e6b04dfb8b588f331173ed31ef63 Mon Sep 17 00:00:00 2001 From: Mike Hunhoff Date: Fri, 3 Mar 2023 09:55:45 -0700 Subject: [PATCH 27/49] update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 543db9ac..52f6de8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ - match: extend OS characteristic to match OS_ANY to all supported OSes #1324 @mike-hunhoff - explorer: fix exception when plugin loaded in IDA hosted under idat #1341 @mike-hunhoff - extractor: fix IDA and vivisect string and bytes features overlap and tests #1327 #1336 @xusheng6 +- explorer: improve embedded PE detection #1344 @mike-hunhoff ### capa explorer IDA Pro plugin From 4047780c08fe03e66391ca90b4fa65742bcaa9db Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Mar 2023 14:59:16 +0000 Subject: [PATCH 28/49] build(deps): bump tqdm from 4.64.1 to 4.65.0 Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.64.1 to 4.65.0. - [Release notes](https://github.com/tqdm/tqdm/releases) - [Commits](https://github.com/tqdm/tqdm/compare/v4.64.1...v4.65.0) --- updated-dependencies: - dependency-name: tqdm dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c39f4c8b..97a8ff54 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ import os import setuptools requirements = [ - "tqdm==4.64.1", + "tqdm==4.65.0", "pyyaml==6.0", "tabulate==0.9.0", "colorama==0.4.5", From 02dc42154bf97b71ae730e49149e95ed51bfdaf0 Mon Sep 17 00:00:00 2001 From: Mike Hunhoff Date: Mon, 6 Mar 2023 08:53:57 -0700 Subject: [PATCH 29/49] Update CHANGELOG.md Co-authored-by: Willi Ballenthin --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52f6de8e..439b0827 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,7 +27,7 @@ - match: extend OS characteristic to match OS_ANY to all supported OSes #1324 @mike-hunhoff - explorer: fix exception when plugin loaded in IDA hosted under idat #1341 @mike-hunhoff - extractor: fix IDA and vivisect string and bytes features overlap and tests #1327 #1336 @xusheng6 -- explorer: improve embedded PE detection #1344 @mike-hunhoff +- explorer: improve embedded PE detection performance and reduce FP potential #1344 @mike-hunhoff ### capa explorer IDA Pro plugin From 95f23dafe57930ffef8f8df1aed4dacce4389bc8 Mon Sep 17 00:00:00 2001 From: Mike Hunhoff Date: Mon, 6 Mar 2023 08:55:32 -0700 Subject: [PATCH 30/49] Update CHANGELOG.md --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 439b0827..ac3ba707 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,11 +25,11 @@ ### Bug Fixes - extractor: fix vivisect loop detection corner case #1310 @mr-tz - match: extend OS characteristic to match OS_ANY to all supported OSes #1324 @mike-hunhoff -- explorer: fix exception when plugin loaded in IDA hosted under idat #1341 @mike-hunhoff - extractor: fix IDA and vivisect string and bytes features overlap and tests #1327 #1336 @xusheng6 -- explorer: improve embedded PE detection performance and reduce FP potential #1344 @mike-hunhoff ### capa explorer IDA Pro plugin +- fix exception when plugin loaded in IDA hosted under idat #1341 @mike-hunhoff +- improve embedded PE detection performance and reduce FP potential #1344 @mike-hunhoff ### Development From 51286380717dd60be9e2ae1d89b8f8ebd8ccf12c Mon Sep 17 00:00:00 2001 From: manasghandat <95558940+manasghandat@users.noreply.github.com> Date: Thu, 9 Mar 2023 11:58:47 +0530 Subject: [PATCH 31/49] code style: update lint.py (#1352) * code style: update lint.py --- scripts/lint.py | 31 +++++++++---------------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/scripts/lint.py b/scripts/lint.py index c049dd6e..cf56f1a8 100644 --- a/scripts/lint.py +++ b/scripts/lint.py @@ -648,11 +648,11 @@ class FormatStringQuotesIncorrect(Lint): continue if value.style is None: # no quotes - self.recommendation = 'add double quotes to "%s"' % value.value + self.recommendation = f'add double quotes to "{value.value}"' return True if value.style == "'": # single quote - self.recommendation = 'change single quotes to double quotes for "%s"' % value.value + self.recommendation = f'change single quotes to double quotes for "{value.value}"' return True elif isinstance(key, ruamel.yaml.ScalarEvent) and key.value == "substring": @@ -662,11 +662,11 @@ class FormatStringQuotesIncorrect(Lint): continue if value.style is None: # no quotes - self.recommendation = 'add double quotes to "%s"' % value.value + self.recommendation = f'add double quotes to "{value.value}"' return True if value.style == "'": # single quote - self.recommendation = 'change single quotes to double quotes for "%s"' % value.value + self.recommendation = f'change single quotes to double quotes for "{value.value}"' return True else: @@ -816,25 +816,12 @@ def lint_rule(ctx: Context, rule: Rule): # and ends up just producing a lot of noise. if not (is_nursery_rule(rule) and len(violations) == 1 and violations[0].name == "missing examples"): print("") - print( - "%s%s" - % ( - " (nursery) " if is_nursery_rule(rule) else "", - rule.name, - ) - ) + print(f'{" (nursery) " if is_nursery_rule(rule) else ""} {rule.name}') for violation in violations: print( - "%s %s: %s: %s" - % ( - " " if is_nursery_rule(rule) else "", - Lint.WARN if is_nursery_rule(rule) else violation.level, - violation.name, - violation.recommendation, - ) + f"{' ' if is_nursery_rule(rule) else ''} {Lint.WARN if is_nursery_rule(rule) else violation.level}: {violation.name}: {violation.recommendation}" ) - print("") if is_nursery_rule(rule): @@ -860,8 +847,8 @@ def lint_rule(ctx: Context, rule: Rule): if (not lints_failed) and (not lints_warned) and has_examples: print("") - print("%s%s" % (" (nursery) ", rule.name)) - print("%s %s: %s: %s" % (" ", Lint.WARN, green("no lint failures"), "Graduate the rule")) + print(f'{" (nursery) " if is_nursery_rule(rule) else ""} {rule.name}') + print(f" {Lint.WARN}: {green('no lint failures')}: Graduate the rule") print("") else: lints_failed = len(tuple(filter(lambda v: v.level == Lint.FAIL, violations))) @@ -921,7 +908,7 @@ def lint(ctx: Context): with redirecting_print_to_tqdm(): for rule in pbar: name = rule.name - pbar.set_description(width("linting rule: %s" % (name), 48)) + pbar.set_description(width(f"linting rule: {name}", 48)) ret[name] = lint_rule(ctx, rule) return ret From d8f89d49d47ec7a0af7db83a2d0f29588e100c70 Mon Sep 17 00:00:00 2001 From: AG <98327736+ggold7046@users.noreply.github.com> Date: Fri, 10 Mar 2023 01:17:59 +0530 Subject: [PATCH 32/49] Update import-to-bn.py Used f string for enhanced readability. --- scripts/import-to-bn.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/import-to-bn.py b/scripts/import-to-bn.py index b9b48cd8..d157af44 100644 --- a/scripts/import-to-bn.py +++ b/scripts/import-to-bn.py @@ -93,9 +93,9 @@ def load_analysis(bv): rows = sorted(rows) for ns, name, va in rows: if ns: - cmt = "%s (%s)" % (name, ns) + cmt = f"{name} ({ns})" else: - cmt = "%s" % (name,) + cmt = f"{name}" binaryninja.log_info("0x%x: %s" % (va, cmt)) try: From 50935372cab82f0184d9142ffe243360c56fff29 Mon Sep 17 00:00:00 2001 From: AG <98327736+ggold7046@users.noreply.github.com> Date: Fri, 10 Mar 2023 01:36:17 +0530 Subject: [PATCH 33/49] Update import-to-ida.py Updated with f string for enhanced readability. --- scripts/import-to-ida.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/import-to-ida.py b/scripts/import-to-ida.py index e5d4b5ad..a32b45b9 100644 --- a/scripts/import-to-ida.py +++ b/scripts/import-to-ida.py @@ -101,9 +101,9 @@ def main(): rows = sorted(rows) for ns, name, va in rows: if ns: - cmt = "%s (%s)" % (name, ns) + cmt = f"{name} ({ns})" else: - cmt = "%s" % (name,) + cmt = f"{name}" logger.info("0x%x: %s", va, cmt) try: From eaeef59583f38532e91b2b0cb71ce5bb0b6184d8 Mon Sep 17 00:00:00 2001 From: AG <98327736+ggold7046@users.noreply.github.com> Date: Fri, 10 Mar 2023 13:03:04 +0530 Subject: [PATCH 34/49] Update insn.py Updated with f strings for enhanced readability. --- capa/features/insn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/capa/features/insn.py b/capa/features/insn.py index 1f1c0171..030784fe 100644 --- a/capa/features/insn.py +++ b/capa/features/insn.py @@ -31,7 +31,7 @@ class _AccessFeature(Feature, abc.ABC): super().__init__(value, description=description) if access is not None: if access not in VALID_FEATURE_ACCESS: - raise ValueError("%s access type %s not valid" % (self.name, access)) + raise ValueError(f"{self.name} access type {access} not valid") self.access = access def __hash__(self): From df6de3446c14b1a2231741daf76f08124b867aa2 Mon Sep 17 00:00:00 2001 From: AG <98327736+ggold7046@users.noreply.github.com> Date: Fri, 10 Mar 2023 13:10:02 +0530 Subject: [PATCH 35/49] Update file.py Updated with f string for enhanced readability. --- capa/features/extractors/ida/file.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/capa/features/extractors/ida/file.py b/capa/features/extractors/ida/file.py index 532d5a89..a3da4c6a 100644 --- a/capa/features/extractors/ida/file.py +++ b/capa/features/extractors/ida/file.py @@ -106,13 +106,13 @@ def extract_file_import_names() -> Iterator[Tuple[Feature, Address]]: for name in capa.features.extractors.helpers.generate_symbols(info[0], info[1]): yield Import(name), addr dll = info[0] - symbol = "#%d" % (info[2]) + symbol = f"#{info[2]}" elif info[1]: dll = info[0] symbol = info[1] elif info[2]: dll = info[0] - symbol = "#%d" % (info[2]) + symbol = f"#{info[2]}" else: continue @@ -180,7 +180,7 @@ def extract_file_format() -> Iterator[Tuple[Feature, Address]]: # no file type to return when processing a binary file, but we want to continue processing return else: - raise NotImplementedError("unexpected file format: %d" % file_info.filetype) + raise NotImplementedError(f"unexpected file format: {file_info.filetype}") def extract_features() -> Iterator[Tuple[Feature, Address]]: From 7031c68a85187881f307954cbca8d5e20d9e31c9 Mon Sep 17 00:00:00 2001 From: linpeiyu164 Date: Sat, 11 Mar 2023 00:07:24 +0800 Subject: [PATCH 36/49] fix wrong indentation level for args.backend --- capa/main.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/capa/main.py b/capa/main.py index ba03c7a4..617df71f 100644 --- a/capa/main.py +++ b/capa/main.py @@ -853,15 +853,15 @@ def install_common_args(parser, wanted=None): help="select sample format, %s" % format_help, ) - if "backend" in wanted: - parser.add_argument( - "-b", - "--backend", - type=str, - help="select the backend to use", - choices=(BACKEND_VIV,), - default=BACKEND_VIV, - ) + if "backend" in wanted: + parser.add_argument( + "-b", + "--backend", + type=str, + help="select the backend to use", + choices=(BACKEND_VIV,), + default=BACKEND_VIV, + ) if "rules" in wanted: parser.add_argument( From 02e451a2b19e564b03a235af92bc32d3792a5c55 Mon Sep 17 00:00:00 2001 From: AG <98327736+ggold7046@users.noreply.github.com> Date: Sat, 11 Mar 2023 12:29:59 +0530 Subject: [PATCH 37/49] Update profile-memory.py Updated with f string for enhanced readability. --- scripts/profile-memory.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/scripts/profile-memory.py b/scripts/profile-memory.py index 349f6ee9..c6e2df90 100644 --- a/scripts/profile-memory.py +++ b/scripts/profile-memory.py @@ -16,10 +16,10 @@ def display_top(snapshot, key_type="lineno", limit=10): ) top_stats = snapshot.statistics(key_type) - print("Top %s lines" % limit) + print(f"Top {limit} lines") for index, stat in enumerate(top_stats[:limit], 1): frame = stat.traceback[0] - print("#%s: %s:%s: %.1f KiB" % (index, frame.filename, frame.lineno, stat.size / 1024)) + print(f"#{index}: {frame.filename}:{frame.lineno}: {stat.size / 1024:.1f} KiB") line = linecache.getline(frame.filename, frame.lineno).strip() if line: print(" %s" % line) @@ -27,9 +27,9 @@ def display_top(snapshot, key_type="lineno", limit=10): other = top_stats[limit:] if other: size = sum(stat.size for stat in other) - print("%s other: %.1f KiB" % (len(other), size / 1024)) + print(f"{len(other)} other: {size / 1024:.1f} KiB") total = sum(stat.size for stat in top_stats) - print("Total allocated size: %.1f KiB" % (total / 1024)) + print(f"Total allocated size: {total / 1024:.1f} KiB") def main(): @@ -45,11 +45,11 @@ def main(): import capa.main count = int(os.environ.get("CAPA_PROFILE_COUNT", 1)) - print("total iterations planned: %d (set via env var CAPA_PROFILE_COUNT)." % (count)) + print(f"total iterations planned: {count} (set via env var CAPA_PROFILE_COUNT).") print() for i in range(count): - print("iteration %d/%d..." % (i + 1, count)) + print(f"iteration {i + 1}/{count}...") with contextlib.redirect_stdout(io.StringIO()): with contextlib.redirect_stderr(io.StringIO()): t0 = time.time() @@ -59,9 +59,9 @@ def main(): gc.collect() process = psutil.Process(os.getpid()) - print(" duration: %0.02fs" % (t1 - t0)) - print(" rss: %.1f MiB" % (process.memory_info().rss / 1024 / 1024)) - print(" vms: %.1f MiB" % (process.memory_info().vms / 1024 / 1024)) + print(f" duration: {t1 - t0:.02f}s") + print(f" rss: {process.memory_info().rss / 1024 / 1024:.1f} MiB") + print(f" vms: {process.memory_info().vms / 1024 / 1024:.1f} MiB") print("done.") gc.collect() From 6321adc41196c402a61637577082919864f587a2 Mon Sep 17 00:00:00 2001 From: AG <98327736+ggold7046@users.noreply.github.com> Date: Sat, 11 Mar 2023 12:43:22 +0530 Subject: [PATCH 38/49] Update match-function-id.py Updated with f string for enhanced readability. --- scripts/match-function-id.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/match-function-id.py b/scripts/match-function-id.py index e7b1ea38..0daa88bb 100644 --- a/scripts/match-function-id.py +++ b/scripts/match-function-id.py @@ -125,7 +125,7 @@ def main(argv=None): for analyzer in analyzers: name = viv_utils.flirt.match_function_flirt_signatures(analyzer.matcher, vw, function) if name: - print("0x%04x: %s" % (function, name)) + print(f"0x{function:04x}: {name}") return 0 From 4fd6f17ced98841508d527f8bb3f5f4c7c39b7e5 Mon Sep 17 00:00:00 2001 From: Capa Bot Date: Tue, 14 Mar 2023 07:34:15 +0000 Subject: [PATCH 39/49] Sync capa rules submodule --- CHANGELOG.md | 9 ++++++++- README.md | 2 +- rules | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac3ba707..13602fc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ ### Breaking Changes -### New Rules (12) +### New Rules (19) - persistence/scheduled-tasks/schedule-task-via-at joren485 - data-manipulation/prng/generate-random-numbers-via-rtlgenrandom william.ballenthin@mandiant.com @@ -20,6 +20,13 @@ - nursery/get-http-request-uri william.ballenthin@mandiant.com - nursery/create-zip-archive-in-dotnet michael.hunhoff@mandiant.com - nursery/extract-zip-archive-in-dotnet anushka.virgaonkar@mandiant.com michael.hunhoff@mandiant.com +- data-manipulation/encryption/tea/decrypt-data-using-tea william.ballenthin@mandiant.com raymond.leong@mandiant.com +- data-manipulation/encryption/tea/encrypt-data-using-tea william.ballenthin@mandiant.com raymond.leong@mandiant.com +- data-manipulation/encryption/xtea/encrypt-data-using-xtea raymond.leong@mandiant.com +- data-manipulation/encryption/xxtea/encrypt-data-using-xxtea raymond.leong@mandiant.com +- nursery/hash-data-using-ripemd128 raymond.leong@mandiant.com +- nursery/hash-data-using-ripemd256 raymond.leong@mandiant.com +- nursery/hash-data-using-ripemd320 raymond.leong@mandiant.com - ### Bug Fixes diff --git a/README.md b/README.md index cdf94867..64917f6a 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![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-781-blue.svg)](https://github.com/mandiant/capa-rules) +[![Number of rules](https://img.shields.io/badge/rules-786-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) diff --git a/rules b/rules index 5351554f..8c9f344c 160000 --- a/rules +++ b/rules @@ -1 +1 @@ -Subproject commit 5351554ff9a1b379161682054847125e15cc5efb +Subproject commit 8c9f344c517c90eae40671900bdf37ff6414dfae From 201330295cadb624936cc507ed08078b556b081d Mon Sep 17 00:00:00 2001 From: Capa Bot Date: Tue, 14 Mar 2023 16:25:56 +0000 Subject: [PATCH 40/49] Sync capa rules submodule --- CHANGELOG.md | 3 ++- README.md | 2 +- rules | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13602fc2..13f72602 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ ### Breaking Changes -### New Rules (19) +### New Rules (20) - persistence/scheduled-tasks/schedule-task-via-at joren485 - data-manipulation/prng/generate-random-numbers-via-rtlgenrandom william.ballenthin@mandiant.com @@ -27,6 +27,7 @@ - nursery/hash-data-using-ripemd128 raymond.leong@mandiant.com - nursery/hash-data-using-ripemd256 raymond.leong@mandiant.com - nursery/hash-data-using-ripemd320 raymond.leong@mandiant.com +- nursery/set-web-proxy-in-dotnet michael.hunhoff@mandiant.com - ### Bug Fixes diff --git a/README.md b/README.md index 64917f6a..6e5db2ac 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![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-786-blue.svg)](https://github.com/mandiant/capa-rules) +[![Number of rules](https://img.shields.io/badge/rules-787-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) diff --git a/rules b/rules index 8c9f344c..95216575 160000 --- a/rules +++ b/rules @@ -1 +1 @@ -Subproject commit 8c9f344c517c90eae40671900bdf37ff6414dfae +Subproject commit 95216575871e545f9dc81c2d3d8bf074a984366f From 74009eb4a486ae42565dbc62012a60d996007387 Mon Sep 17 00:00:00 2001 From: Harsh Mehta <73291466+1nf3rn0-H@users.noreply.github.com> Date: Tue, 14 Mar 2023 22:28:43 +0530 Subject: [PATCH 41/49] Updated Copyright (#1383) * Updated Copyright --- tests/fixtures.py | 2 +- tests/test_engine.py | 2 +- tests/test_fmt.py | 2 +- tests/test_freeze.py | 2 +- tests/test_helpers.py | 2 +- tests/test_main.py | 2 +- tests/test_match.py | 2 +- tests/test_optimizer.py | 2 +- tests/test_os_detection.py | 2 +- tests/test_pefile_features.py | 2 +- tests/test_result_document.py | 2 +- tests/test_rule_cache.py | 2 +- tests/test_rules.py | 2 +- tests/test_rules_insn_scope.py | 2 +- tests/test_scripts.py | 2 +- tests/test_viv_features.py | 2 +- 16 files changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/fixtures.py b/tests/fixtures.py index 1c4d2ad3..bfbbbdb3 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# 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 diff --git a/tests/test_engine.py b/tests/test_engine.py index 89c3b739..09560257 100644 --- a/tests/test_engine.py +++ b/tests/test_engine.py @@ -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 diff --git a/tests/test_fmt.py b/tests/test_fmt.py index 1f37886c..96101dfb 100644 --- a/tests/test_fmt.py +++ b/tests/test_fmt.py @@ -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 diff --git a/tests/test_freeze.py b/tests/test_freeze.py index 664afd44..2c5f1920 100644 --- a/tests/test_freeze.py +++ b/tests/test_freeze.py @@ -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 diff --git a/tests/test_helpers.py b/tests/test_helpers.py index eab1efa4..90b689b3 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -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 diff --git a/tests/test_main.py b/tests/test_main.py index e5b7cd08..d515c104 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# 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 diff --git a/tests/test_match.py b/tests/test_match.py index 510db98f..fc3583f7 100644 --- a/tests/test_match.py +++ b/tests/test_match.py @@ -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 diff --git a/tests/test_optimizer.py b/tests/test_optimizer.py index 69a79bd6..d07ba330 100644 --- a/tests/test_optimizer.py +++ b/tests/test_optimizer.py @@ -1,4 +1,4 @@ -# Copyright (C) 2021 FireEye, Inc. All Rights Reserved. +# Copyright (C) 2021 Mandiant, Inc. All Rights Reserved. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at: [package root]/LICENSE.txt diff --git a/tests/test_os_detection.py b/tests/test_os_detection.py index 82e592a7..bdc89686 100644 --- a/tests/test_os_detection.py +++ b/tests/test_os_detection.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2022 FireEye, Inc. All Rights Reserved. +# Copyright (C) 2022 Mandiant, Inc. All Rights Reserved. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at: [package root]/LICENSE.txt diff --git a/tests/test_pefile_features.py b/tests/test_pefile_features.py index 2e1afc7b..e0b735e8 100644 --- a/tests/test_pefile_features.py +++ b/tests/test_pefile_features.py @@ -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 diff --git a/tests/test_result_document.py b/tests/test_result_document.py index b98fadff..8172c601 100644 --- a/tests/test_result_document.py +++ b/tests/test_result_document.py @@ -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 diff --git a/tests/test_rule_cache.py b/tests/test_rule_cache.py index fb11e5e7..b52e2577 100644 --- a/tests/test_rule_cache.py +++ b/tests/test_rule_cache.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 FireEye, Inc. All Rights Reserved. +# Copyright (C) 2023 Mandiant, Inc. All Rights Reserved. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at: [package root]/LICENSE.txt diff --git a/tests/test_rules.py b/tests/test_rules.py index fe154c39..29db2a2f 100644 --- a/tests/test_rules.py +++ b/tests/test_rules.py @@ -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 diff --git a/tests/test_rules_insn_scope.py b/tests/test_rules_insn_scope.py index 27d07489..481b3cd9 100644 --- a/tests/test_rules_insn_scope.py +++ b/tests/test_rules_insn_scope.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022 FireEye, Inc. All Rights Reserved. +# Copyright (C) 2022 Mandiant, Inc. All Rights Reserved. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at: [package root]/LICENSE.txt diff --git a/tests/test_scripts.py b/tests/test_scripts.py index b519f89e..f5a9d701 100644 --- a/tests/test_scripts.py +++ b/tests/test_scripts.py @@ -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 diff --git a/tests/test_viv_features.py b/tests/test_viv_features.py index fa8bfda3..fcf49c84 100644 --- a/tests/test_viv_features.py +++ b/tests/test_viv_features.py @@ -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 From 22a1a8e41f716992d14229b42dd7cbb8a2b6f6a1 Mon Sep 17 00:00:00 2001 From: Capa Bot Date: Tue, 14 Mar 2023 18:30:53 +0000 Subject: [PATCH 42/49] Sync capa rules submodule --- rules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules b/rules index 95216575..977ad92e 160000 --- a/rules +++ b/rules @@ -1 +1 @@ -Subproject commit 95216575871e545f9dc81c2d3d8bf074a984366f +Subproject commit 977ad92ea36822a76bc6885fcbf82f21063b2e8f From 8cf74759a6003c8f5b4d4c79388a6d3a3cd2a91b Mon Sep 17 00:00:00 2001 From: Capa Bot Date: Tue, 14 Mar 2023 18:35:45 +0000 Subject: [PATCH 43/49] Sync capa rules submodule --- rules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules b/rules index 977ad92e..6bd733a2 160000 --- a/rules +++ b/rules @@ -1 +1 @@ -Subproject commit 977ad92ea36822a76bc6885fcbf82f21063b2e8f +Subproject commit 6bd733a2e38b7199ec0586ad6ff30288e7a426d4 From 1336796c0cf369fc0bd27f2c7838a9466af16ed2 Mon Sep 17 00:00:00 2001 From: manasghandat <95558940+manasghandat@users.noreply.github.com> Date: Thu, 16 Mar 2023 15:46:18 +0530 Subject: [PATCH 44/49] code style : update remaining files (#1353) * code style: update string formatting using fstrings --------- Co-authored-by: Willi Ballenthin Co-authored-by: Moritz --- capa/engine.py | 10 ++-- capa/features/common.py | 24 ++++---- capa/features/extractors/elf.py | 4 +- capa/features/extractors/helpers.py | 4 +- capa/features/extractors/ida/basicblock.py | 2 +- capa/features/extractors/ida/helpers.py | 2 +- capa/features/extractors/pefile.py | 2 +- capa/features/extractors/viv/basicblock.py | 2 +- capa/features/extractors/viv/file.py | 2 +- capa/features/freeze/__init__.py | 2 +- capa/features/insn.py | 8 +-- capa/helpers.py | 6 +- capa/ida/helpers.py | 2 +- capa/ida/plugin/extractor.py | 4 +- capa/ida/plugin/form.py | 34 +++++------ capa/ida/plugin/item.py | 2 +- capa/ida/plugin/model.py | 30 +++++----- capa/ida/plugin/view.py | 66 ++++++++++------------ capa/main.py | 16 +++--- capa/render/default.py | 10 ++-- capa/render/utils.py | 2 +- capa/render/verbose.py | 2 +- capa/render/vverbose.py | 38 ++++++------- capa/rules/__init__.py | 41 +++++++------- scripts/bulk-process.py | 6 +- scripts/capa_as_library.py | 10 ++-- scripts/import-to-bn.py | 4 +- scripts/import-to-ida.py | 4 +- scripts/profile-memory.py | 16 +++--- scripts/profile-time.py | 6 +- scripts/show-capabilities-by-function.py | 2 +- scripts/show-features.py | 34 +++++------ tests/fixtures.py | 15 ++--- tests/test_ida_features.py | 14 ++--- tests/test_scripts.py | 2 +- 35 files changed, 201 insertions(+), 227 deletions(-) diff --git a/capa/engine.py b/capa/engine.py index dde1e7c8..4d9e0980 100644 --- a/capa/engine.py +++ b/capa/engine.py @@ -43,10 +43,12 @@ class Statement: self.description = description def __str__(self): + name = self.name.lower() + children = ",".join(map(str, self.get_children())) if self.description: - return "%s(%s = %s)" % (self.name.lower(), ",".join(map(str, self.get_children())), self.description) + return f"{name}({children} = {self.description})" else: - return "%s(%s)" % (self.name.lower(), ",".join(map(str, self.get_children()))) + return f"{name}({children})" def __repr__(self): return str(self) @@ -232,9 +234,9 @@ class Range(Statement): def __str__(self): if self.max == (1 << 64 - 1): - return "range(%s, min=%d, max=infinity)" % (str(self.child), self.min) + return f"range({str(self.child)}, min={self.min}, max=infinity)" else: - return "range(%s, min=%d, max=%d)" % (str(self.child), self.min, self.max) + return f"range({str(self.child)}, min={self.min}, max={self.max})" class Subscope(Statement): diff --git a/capa/features/common.py b/capa/features/common.py index c908d7bc..d2f1a4ff 100644 --- a/capa/features/common.py +++ b/capa/features/common.py @@ -149,11 +149,11 @@ class Feature(abc.ABC): def __str__(self): if self.value is not None: if self.description: - return "%s(%s = %s)" % (self.get_name_str(), self.get_value_str(), self.description) + return f"{self.get_name_str()}({self.get_value_str()} = {self.description})" else: - return "%s(%s)" % (self.get_name_str(), self.get_value_str()) + return f"{self.get_name_str()}({self.get_value_str()})" else: - return "%s" % self.get_name_str() + return f"{self.get_name_str()}" def __repr__(self): return str(self) @@ -242,7 +242,7 @@ class Substring(String): def __str__(self): assert isinstance(self.value, str) - return "substring(%s)" % escape_string(self.value) + return f"substring({escape_string(self.value)})" class _MatchedSubstring(Substring): @@ -267,11 +267,9 @@ class _MatchedSubstring(Substring): self.matches = matches def __str__(self): + matches = ", ".join(map(lambda s: '"' + s + '"', (self.matches or {}).keys())) assert isinstance(self.value, str) - return 'substring("%s", matches = %s)' % ( - self.value, - ", ".join(map(lambda s: '"' + s + '"', (self.matches or {}).keys())), - ) + return f'substring("{self.value}", matches = {matches})' class Regex(String): @@ -290,7 +288,7 @@ class Regex(String): if value.endswith("/i"): value = value[: -len("i")] raise ValueError( - "invalid regular expression: %s it should use Python syntax, try it at https://pythex.org" % value + f"invalid regular expression: {value} it should use Python syntax, try it at https://pythex.org" ) from exc def evaluate(self, ctx, short_circuit=True): @@ -336,7 +334,7 @@ class Regex(String): def __str__(self): assert isinstance(self.value, str) - return "regex(string =~ %s)" % self.value + return f"regex(string =~ {self.value})" class _MatchedRegex(Regex): @@ -361,11 +359,9 @@ class _MatchedRegex(Regex): self.matches = matches def __str__(self): + matches = ", ".join(map(lambda s: '"' + s + '"', (self.matches or {}).keys())) assert isinstance(self.value, str) - return "regex(string =~ %s, matches = %s)" % ( - self.value, - ", ".join(map(lambda s: '"' + s + '"', (self.matches or {}).keys())), - ) + return f"regex(string =~ {self.value}, matches = {matches})" class StringFactory: diff --git a/capa/features/extractors/elf.py b/capa/features/extractors/elf.py index 08467481..ab79cc74 100644 --- a/capa/features/extractors/elf.py +++ b/capa/features/extractors/elf.py @@ -121,14 +121,14 @@ class ELF: elif ei_class == 2: self.bitness = 64 else: - raise CorruptElfFile("invalid ei_class: 0x%02x" % ei_class) + raise CorruptElfFile(f"invalid ei_class: 0x{ei_class:02x}") if ei_data == 1: self.endian = "<" elif ei_data == 2: self.endian = ">" else: - raise CorruptElfFile("not an ELF file: invalid ei_data: 0x%02x" % ei_data) + raise CorruptElfFile(f"not an ELF file: invalid ei_data: 0x{ei_data:02x}") if self.bitness == 32: e_phoff, e_shoff = struct.unpack_from(self.endian + "II", self.file_header, 0x1C) diff --git a/capa/features/extractors/helpers.py b/capa/features/extractors/helpers.py index 1e2ff2cb..d27b85b1 100644 --- a/capa/features/extractors/helpers.py +++ b/capa/features/extractors/helpers.py @@ -55,7 +55,7 @@ def generate_symbols(dll: str, symbol: str) -> Iterator[str]: dll = dll.lower() # kernel32.CreateFileA - yield "%s.%s" % (dll, symbol) + yield f"{dll}.{symbol}" if not is_ordinal(symbol): # CreateFileA @@ -63,7 +63,7 @@ def generate_symbols(dll: str, symbol: str) -> Iterator[str]: if is_aw_function(symbol): # kernel32.CreateFile - yield "%s.%s" % (dll, symbol[:-1]) + yield f"{dll}.{symbol[:-1]}" if not is_ordinal(symbol): # CreateFile diff --git a/capa/features/extractors/ida/basicblock.py b/capa/features/extractors/ida/basicblock.py index 20633e8a..34f7d1ce 100644 --- a/capa/features/extractors/ida/basicblock.py +++ b/capa/features/extractors/ida/basicblock.py @@ -34,7 +34,7 @@ def get_printable_len(op: idaapi.op_t) -> int: elif op.dtype == idaapi.dt_qword: chars = struct.pack(" Iterator[int]: end: max virtual address seq: bytes to search e.g. b"\x01\x03" """ - seqstr = " ".join(["%02x" % b for b in seq]) + seqstr = " ".join([f"{b:02x}" for b in seq]) while True: # TODO find_binary: Deprecated. Please use ida_bytes.bin_search() instead. ea = idaapi.find_binary(start, end, seqstr, 0, idaapi.SEARCH_DOWN) diff --git a/capa/features/extractors/pefile.py b/capa/features/extractors/pefile.py index 038200b8..cf4f16c4 100644 --- a/capa/features/extractors/pefile.py +++ b/capa/features/extractors/pefile.py @@ -64,7 +64,7 @@ def extract_file_import_names(pe, **kwargs): for imp in dll.imports: if imp.import_by_ordinal: - impname = "#%s" % imp.ordinal + impname = f"#{imp.ordinal}" else: try: impname = imp.name.partition(b"\x00")[0].decode("ascii") diff --git a/capa/features/extractors/viv/basicblock.py b/capa/features/extractors/viv/basicblock.py index 9848bec0..24a753ae 100644 --- a/capa/features/extractors/viv/basicblock.py +++ b/capa/features/extractors/viv/basicblock.py @@ -121,7 +121,7 @@ def get_printable_len(oper: envi.archs.i386.disasm.i386ImmOper) -> int: elif oper.tsize == 8: chars = struct.pack(" Iterator[Tuple[Feature, Address]] modname, impname = tinfo.split(".", 1) if is_viv_ord_impname(impname): # replace ord prefix with # - impname = "#%s" % impname[len("ord") :] + impname = "#" + impname[len("ord") :] addr = AbsoluteVirtualAddress(va) for name in capa.features.extractors.helpers.generate_symbols(modname, impname): diff --git a/capa/features/freeze/__init__.py b/capa/features/freeze/__init__.py index fd3dcdb9..e6911c5f 100644 --- a/capa/features/freeze/__init__.py +++ b/capa/features/freeze/__init__.py @@ -329,7 +329,7 @@ def loads(s: str) -> capa.features.extractors.base_extractor.FeatureExtractor: freeze = Freeze.parse_raw(s) if freeze.version != 2: - raise ValueError("unsupported freeze format version: %d", freeze.version) + raise ValueError(f"unsupported freeze format version: {freeze.version}") return null.NullFeatureExtractor( base_address=freeze.base_address.to_capa(), diff --git a/capa/features/insn.py b/capa/features/insn.py index 030784fe..cc4d54e6 100644 --- a/capa/features/insn.py +++ b/capa/features/insn.py @@ -15,9 +15,9 @@ from capa.features.common import VALID_FEATURE_ACCESS, Feature def hex(n: int) -> str: """render the given number using upper case hex, like: 0x123ABC""" if n < 0: - return "-0x%X" % (-n) + return f"-0x{(-n):X}" else: - return "0x%X" % n + return f"0x{(n):X}" class API(Feature): @@ -105,7 +105,7 @@ class _Operand(Feature, abc.ABC): class OperandNumber(_Operand): # cached names so we don't do extra string formatting every ctor - NAMES = ["operand[%d].number" % i for i in range(MAX_OPERAND_COUNT)] + NAMES = [f"operand[{i}].number" for i in range(MAX_OPERAND_COUNT)] # operand[i].number: 0x12 def __init__(self, index: int, value: int, description=None): @@ -119,7 +119,7 @@ class OperandNumber(_Operand): class OperandOffset(_Operand): # cached names so we don't do extra string formatting every ctor - NAMES = ["operand[%d].offset" % i for i in range(MAX_OPERAND_COUNT)] + NAMES = [f"operand[{i}].offset" for i in range(MAX_OPERAND_COUNT)] # operand[i].offset: 0x12 def __init__(self, index: int, value: int, description=None): diff --git a/capa/helpers.py b/capa/helpers.py index a2edc812..d0b2123b 100644 --- a/capa/helpers.py +++ b/capa/helpers.py @@ -22,14 +22,14 @@ logger = logging.getLogger("capa") def hex(n: int) -> str: """render the given number using upper case hex, like: 0x123ABC""" if n < 0: - return "-0x%X" % (-n) + return f"-0x{(-n):X}" else: - return "0x%X" % n + return f"0x{(n):X}" def get_file_taste(sample_path: str) -> bytes: if not os.path.exists(sample_path): - raise IOError("sample path %s does not exist or cannot be accessed" % sample_path) + raise IOError(f"sample path {sample_path} does not exist or cannot be accessed") with open(sample_path, "rb") as f: taste = f.read(8) return taste diff --git a/capa/ida/helpers.py b/capa/ida/helpers.py index 86584410..fbd502fe 100644 --- a/capa/ida/helpers.py +++ b/capa/ida/helpers.py @@ -45,7 +45,7 @@ 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) + idaapi.info(f"{message}. Please refer to IDA Output window for more information.") def is_supported_ida_version(): diff --git a/capa/ida/plugin/extractor.py b/capa/ida/plugin/extractor.py index a6464020..58bbc4ba 100644 --- a/capa/ida/plugin/extractor.py +++ b/capa/ida/plugin/extractor.py @@ -26,7 +26,7 @@ class CapaExplorerProgressIndicator(QtCore.QObject): """ if ida_kernwin.user_cancelled(): raise UserCancelledError("user cancelled") - self.progress.emit("extracting features from %s" % text) + self.progress.emit(f"extracting features from {text}") class CapaExplorerFeatureExtractor(IdaFeatureExtractor): @@ -40,5 +40,5 @@ class CapaExplorerFeatureExtractor(IdaFeatureExtractor): self.indicator = CapaExplorerProgressIndicator() def extract_function_features(self, fh: FunctionHandle): - self.indicator.update("function at 0x%X" % fh.inner.start_ea) + self.indicator.update(f"function at {hex(fh.inner.start_ea)}") return super().extract_function_features(fh) diff --git a/capa/ida/plugin/form.py b/capa/ida/plugin/form.py index 2f30928d..6084277d 100644 --- a/capa/ida/plugin/form.py +++ b/capa/ida/plugin/form.py @@ -83,13 +83,13 @@ def trim_function_name(f, max_length=25): """ """ n = idaapi.get_name(f.start_ea) if len(n) > max_length: - n = "%s..." % n[:max_length] + n = f"{n[:max_length]}..." return n def update_wait_box(text): """update the IDA wait box""" - ida_kernwin.replace_wait_box("capa explorer...%s" % text) + ida_kernwin.replace_wait_box(f"capa explorer...{text}") class QLineEditClicked(QtWidgets.QLineEdit): @@ -630,7 +630,7 @@ class CapaExplorerForm(idaapi.PluginForm): try: def on_load_rule(_, i, total): - update_wait_box("loading capa rules from %s (%d of %d)" % (rule_path, i + 1, total)) + update_wait_box(f"loading capa rules from {rule_path} ({i+1} of {total})") if ida_kernwin.user_cancelled(): raise UserCancelledError("user cancelled") @@ -640,7 +640,7 @@ class CapaExplorerForm(idaapi.PluginForm): return None except Exception as e: capa.ida.helpers.inform_user_ida_ui( - "Failed to load capa rules from %s" % settings.user[CAPA_SETTINGS_RULE_PATH] + f"Failed to load capa rules from {settings.user[CAPA_SETTINGS_RULE_PATH]}" ) logger.error("Failed to load capa rules from %s (error: %s).", settings.user[CAPA_SETTINGS_RULE_PATH], e) @@ -691,10 +691,9 @@ class CapaExplorerForm(idaapi.PluginForm): update_wait_box("verifying cached results") - view_status_rules: str = "%s (%d rules)" % ( - settings.user[CAPA_SETTINGS_RULE_PATH], - self.program_analysis_ruleset_cache.source_rule_count, - ) + count_source_rules = self.program_analysis_ruleset_cache.source_rule_count + user_settings = settings.user[CAPA_SETTINGS_RULE_PATH] + view_status_rules: str = f"{user_settings} ({count_source_rules} rules)" # warn user about potentially outdated rules, depending on the use-case this may be expected if ( @@ -710,10 +709,8 @@ class CapaExplorerForm(idaapi.PluginForm): ) view_status_rules = "no rules matched for cache" - new_view_status = "capa rules: %s, cached results (created %s)" % ( - view_status_rules, - self.resdoc_cache.meta.timestamp.strftime("%Y-%m-%d %H:%M:%S"), - ) + cached_results_time = self.resdoc_cache.meta.timestamp.strftime("%Y-%m-%d %H:%M:%S") + new_view_status = f"capa rules: {view_status_rules}, cached results (created {cached_results_time})" except Exception as e: logger.error("Failed to load cached capa results (error: %s).", e, exc_info=True) return False @@ -725,7 +722,7 @@ class CapaExplorerForm(idaapi.PluginForm): def slot_progress_feature_extraction(text): """slot function to handle feature extraction progress updates""" - update_wait_box("%s (%d of %d)" % (text, self.process_count, self.process_total)) + update_wait_box(f"{text} ({self.process_count} of {self.process_total})") self.process_count += 1 update_wait_box("initializing feature extractor") @@ -843,12 +840,9 @@ class CapaExplorerForm(idaapi.PluginForm): except Exception as e: logger.error("Failed to save results to database (error: %s)", e, exc_info=True) return False - - new_view_status = "capa rules: %s (%d rules)" % ( - settings.user[CAPA_SETTINGS_RULE_PATH], - self.program_analysis_ruleset_cache.source_rule_count, - ) - + user_settings = settings.user[CAPA_SETTINGS_RULE_PATH] + count_source_rules = self.program_analysis_ruleset_cache.source_rule_count + new_view_status = f"capa rules: {user_settings} ({count_source_rules} rules)" # regardless of new analysis, render results - e.g. we may only want to render results after checking # show results by function @@ -1094,7 +1088,7 @@ class CapaExplorerForm(idaapi.PluginForm): self.view_rulegen_features.load_features(all_file_features, all_function_features) self.set_view_status_label( - "capa rules: %s (%d rules)" % (settings.user[CAPA_SETTINGS_RULE_PATH], ruleset.source_rule_count) + f"capa rules: {settings.user[CAPA_SETTINGS_RULE_PATH]} ({settings.user[CAPA_SETTINGS_RULE_PATH]} rules)" ) except Exception as e: logger.error("Failed to render views (error: %s)", e, exc_info=True) diff --git a/capa/ida/plugin/item.py b/capa/ida/plugin/item.py index ac349424..6d0eee23 100644 --- a/capa/ida/plugin/item.py +++ b/capa/ida/plugin/item.py @@ -30,7 +30,7 @@ def info_to_name(display): def ea_to_hex(ea): """convert effective address (ea) to hex for display""" - return "%08X" % ea + return f"{hex(ea)}" class CapaExplorerDataItem: diff --git a/capa/ida/plugin/model.py b/capa/ida/plugin/model.py index 20d9be88..b53007ae 100644 --- a/capa/ida/plugin/model.py +++ b/capa/ida/plugin/model.py @@ -369,34 +369,34 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel): if statement.type != rd.CompoundStatementType.NOT: display = statement.type if statement.description: - display += " (%s)" % statement.description + display += f" ({statement.description})" return CapaExplorerDefaultItem(parent, display) elif isinstance(statement, rd.CompoundStatement) and statement.type == rd.CompoundStatementType.NOT: # TODO: do we display 'not' pass elif isinstance(statement, rd.SomeStatement): - display = "%d or more" % statement.count + display = f"{statement.count} or more" if statement.description: - display += " (%s)" % statement.description + display += f" ({statement.description})" return CapaExplorerDefaultItem(parent, display) 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 = f"count({self.capa_doc_feature_to_display(statement.child)}): " if statement.max == statement.min: - display += "%d" % (statement.min) + display += f"{statement.min}" elif statement.min == 0: - display += "%d or fewer" % (statement.max) + display += f"{statement.max} or fewer" elif statement.max == (1 << 64 - 1): - display += "%d or more" % (statement.min) + display += f"{statement.min} or more" else: - display += "between %d and %d" % (statement.min, statement.max) + display += f"between {statement.min} and {statement.max}" if statement.description: - display += " (%s)" % statement.description + display += f" ({statement.description})" parent2 = CapaExplorerFeatureItem(parent, display=display) @@ -408,7 +408,7 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel): elif isinstance(statement, rd.SubscopeStatement): display = str(statement.scope) if statement.description: - display += " (%s)" % statement.description + display += f" ({statement.description})" return CapaExplorerSubscopeItem(parent, display) else: raise RuntimeError("unexpected match statement type: " + str(statement)) @@ -537,7 +537,7 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel): if value: if isinstance(feature, frzf.StringFeature): - value = '"%s"' % capa.features.common.escape_string(value) + value = f'"{capa.features.common.escape_string(value)}"' if isinstance(feature, frzf.PropertyFeature) and feature.access is not None: key = f"property/{feature.access}" @@ -547,11 +547,11 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel): key = f"operand[{feature.index}].offset" if feature.description: - return "%s(%s = %s)" % (key, value, feature.description) + return f"{key}({value} = {feature.description})" else: - return "%s(%s)" % (key, value) + return f"{key}({value})" else: - return "%s" % key + return f"{key}" def render_capa_doc_feature_node( self, @@ -669,7 +669,7 @@ class CapaExplorerDataModel(QtCore.QAbstractItemModel): elif isinstance(feature, frzf.StringFeature): # display string preview return CapaExplorerStringViewItem( - parent, display, location, '"%s"' % capa.features.common.escape_string(feature.string) + parent, display, location, f'"{capa.features.common.escape_string(feature.string)}"' ) elif isinstance( diff --git a/capa/ida/plugin/view.py b/capa/ida/plugin/view.py index 075d1ff3..93217028 100644 --- a/capa/ida/plugin/view.py +++ b/capa/ida/plugin/view.py @@ -58,7 +58,7 @@ def parse_yaml_line(feature): if m: # reconstruct count without description feature, value, description, count = m.groups() - feature = "- count(%s(%s)): %s" % (feature, value, count) + feature = f"- count({feature}({value})): {count}" elif not feature.startswith("#"): feature, _, comment = feature.partition("#") feature, _, description = feature.partition("=") @@ -72,18 +72,18 @@ def parse_node_for_feature(feature, description, comment, depth): display = "" if feature.startswith("#"): - display += "%s%s\n" % (" " * depth, feature) + display += f"{' '*depth}{feature}\n" elif description: if feature.startswith(("- and", "- or", "- optional", "- basic block", "- not", "- instruction:")): - display += "%s%s" % (" " * depth, feature) + display += f"{' '*depth}{feature}\n" if comment: - display += " # %s" % comment - display += "\n%s- description: %s\n" % (" " * (depth + 2), description) + display += f" # {comment}" + display += f"\n{' '*(depth+2)}- description: {description}\n" elif feature.startswith("- string"): - display += "%s%s" % (" " * depth, feature) + display += f"{' '*depth}{feature}\n" if comment: - display += " # %s" % comment - display += "\n%sdescription: %s\n" % (" " * (depth + 2), description) + display += f" # {comment}" + display += f"\n{' '*(depth+2)}description: {description}\n" elif feature.startswith("- count"): # count is weird, we need to format description based on feature type, so we parse with regex # assume format - count(()): @@ -91,28 +91,22 @@ def parse_node_for_feature(feature, description, comment, depth): if m: name, value, count = m.groups() if name in ("string",): - display += "%s%s" % (" " * depth, feature) + display += f"{' '*depth}{feature}" if comment: display += " # %s" % comment - display += "\n%sdescription: %s\n" % (" " * (depth + 2), description) + display += f"\n{' '*(depth+2)}description: {description}\n" else: - display += "%s- count(%s(%s = %s)): %s" % ( - " " * depth, - name, - value, - description, - count, - ) + display += f"{' '*depth}- count({name}({value} = {description})): {count}" if comment: - display += " # %s\n" % comment + display += f" # {comment}\n" else: - display += "%s%s = %s" % (" " * depth, feature, description) + display += f"{' '*depth}{feature} = {description}" if comment: - display += " # %s\n" % comment + display += f" # {comment}\n" else: - display += "%s%s" % (" " * depth, feature) + display += f"{' '*depth}{feature}" if comment: - display += " # %s\n" % comment + display += f" # {comment}\n" return display if display.endswith("\n") else display + "\n" @@ -198,14 +192,14 @@ class CapaExplorerRulegenPreview(QtWidgets.QTextEdit): " name: ", " namespace: ", " authors:", - " - %s" % author, - " scope: %s" % scope, + f" - {author}", + f" scope: {scope}", " references:", " - ", " examples:", - " - %s:0x%X" % (capa.ida.helpers.get_file_md5().upper(), ea) + f" - {capa.ida.helpers.get_file_md5().upper()}:{hex(ea)}" if ea - else " - %s" % (capa.ida.helpers.get_file_md5().upper()), + else f" - {capa.ida.helpers.get_file_md5().upper()}", " features:", ] self.setText("\n".join(metadata_default)) @@ -539,7 +533,7 @@ class CapaExplorerRulegenEditor(QtWidgets.QTreeWidget): # build submenu with modify actions sub_menu = build_context_menu(self.parent(), sub_actions) - sub_menu.setTitle("Nest feature%s" % ("" if len(tuple(self.get_features(selected=True))) == 1 else "s")) + sub_menu.setTitle(f"Nest feature{'' if len(tuple(self.get_features(selected=True))) == 1 else 's'}") # build main menu with submenu + main actions menu = build_context_menu(self.parent(), (sub_menu,) + actions) @@ -654,21 +648,21 @@ class CapaExplorerRulegenEditor(QtWidgets.QTreeWidget): # single features for k, v in filter(lambda t: t[1] == 1, counted): if isinstance(k, (capa.features.common.String,)): - value = '"%s"' % capa.features.common.escape_string(k.get_value_str()) + value = f'"{capa.features.common.escape_string(k.get_value_str())}"' else: value = k.get_value_str() - self.new_feature_node(top_node, ("- %s: %s" % (k.name.lower(), value), "")) + self.new_feature_node(top_node, (f"- {k.name.lower()}: {value}", "")) # n > 1 features for k, v in filter(lambda t: t[1] > 1, counted): if k.value: if isinstance(k, (capa.features.common.String,)): - value = '"%s"' % capa.features.common.escape_string(k.get_value_str()) + value = f'"{capa.features.common.escape_string(k.get_value_str())}"' else: value = k.get_value_str() - display = "- count(%s(%s)): %d" % (k.name.lower(), value, v) + display = f"- count({k.name.lower()}({value})): {v}" else: - display = "- count(%s): %d" % (k.name.lower(), v) + display = f"- count({k.name.lower()}): {v}" self.new_feature_node(top_node, (display, "")) self.update_preview() @@ -880,7 +874,7 @@ class CapaExplorerRulegenFeatures(QtWidgets.QTreeWidget): if isinstance(self.selectedItems()[0].data(0, 0x100), capa.features.common.Bytes): actions.append(("Add n bytes...", (), self.slot_add_n_bytes_feature)) else: - action_add_features_fmt = "Add %d features" % selected_items_count + action_add_features_fmt = f"Add {selected_items_count} features" actions.append((action_add_features_fmt, (), self.slot_add_selected_features)) @@ -1029,7 +1023,7 @@ class CapaExplorerRulegenFeatures(QtWidgets.QTreeWidget): def format_address(e): if isinstance(e, AbsoluteVirtualAddress): - return "%X" % int(e) + return f"{hex(int(e))}" else: return "" @@ -1038,8 +1032,8 @@ class CapaExplorerRulegenFeatures(QtWidgets.QTreeWidget): name = feature.name.lower() value = feature.get_value_str() if isinstance(feature, (capa.features.common.String,)): - value = '"%s"' % capa.features.common.escape_string(value) - return "%s(%s)" % (name, value) + value = f'"{capa.features.common.escape_string(value)}"' + return f"{name}({value})" for feature, addrs in sorted(features.items(), key=lambda k: sorted(k[1])): if isinstance(feature, capa.features.basicblock.BasicBlock): diff --git a/capa/main.py b/capa/main.py index 617df71f..6d024778 100644 --- a/capa/main.py +++ b/capa/main.py @@ -261,9 +261,9 @@ def find_capabilities(ruleset: RuleSet, extractor: FeatureExtractor, disable_pro logger.debug("skipping library function 0x%x (%s)", f.address, function_name) meta["library_functions"][f.address] = function_name n_libs = len(meta["library_functions"]) - percentage = 100 * (n_libs / n_funcs) + percentage = round(100 * (n_libs / n_funcs)) if isinstance(pb, tqdm.tqdm): - pb.set_postfix_str("skipped %d library functions (%d%%)" % (n_libs, percentage)) + pb.set_postfix_str(f"skipped {n_libs} library functions ({percentage}%)") continue function_matches, bb_matches, insn_matches, feature_count = find_code_capabilities(ruleset, extractor, f) @@ -397,8 +397,8 @@ def get_meta_str(vw): meta = [] for k in ["Format", "Platform", "Architecture"]: if k in vw.metadata: - meta.append("%s: %s" % (k.lower(), vw.metadata[k])) - return "%s, number of functions: %d" % (", ".join(meta), len(vw.getFunctions())) + meta.append(f"{k.lower()}: {vw.metadata[k]}") + return f"{', '.join(meta)}, number of functions: {len(vw.getFunctions())}" def is_running_standalone() -> bool: @@ -569,7 +569,7 @@ def collect_rule_file_paths(rule_paths: List[str]) -> List[str]: rule_file_paths = [] for rule_path in rule_paths: if not os.path.exists(rule_path): - raise IOError("rule path %s does not exist or cannot be accessed" % rule_path) + raise IOError(f"rule path {rule_path} does not exist or cannot be accessed") if os.path.isfile(rule_path): rule_file_paths.append(rule_path) @@ -660,7 +660,7 @@ def get_rules( def get_signatures(sigs_path): if not os.path.exists(sigs_path): - raise IOError("signatures path %s does not exist or cannot be accessed" % sigs_path) + raise IOError(f"signatures path {sigs_path} does not exist or cannot be accessed") paths = [] if os.path.isfile(sigs_path): @@ -844,13 +844,13 @@ def install_common_args(parser, wanted=None): (FORMAT_SC64, "64-bit shellcode"), (FORMAT_FREEZE, "features previously frozen by capa"), ] - format_help = ", ".join(["%s: %s" % (f[0], f[1]) for f in formats]) + format_help = ", ".join([f"{f[0]}: {f[1]}" for f in formats]) parser.add_argument( "-f", "--format", choices=[f[0] for f in formats], default=FORMAT_AUTO, - help="select sample format, %s" % format_help, + help=f"select sample format, {format_help}", ) if "backend" in wanted: diff --git a/capa/render/default.py b/capa/render/default.py index 66f765d6..76659252 100644 --- a/capa/render/default.py +++ b/capa/render/default.py @@ -97,7 +97,7 @@ def render_capabilities(doc: rd.ResultDocument, ostream: StringIO): if count == 1: capability = rutils.bold(rule.meta.name) else: - capability = "%s (%d matches)" % (rutils.bold(rule.meta.name), count) + capability = f"{rutils.bold(rule.meta.name)} ({count} matches)" rows.append((capability, rule.meta.namespace)) if rows: @@ -135,9 +135,9 @@ def render_attack(doc: rd.ResultDocument, ostream: StringIO): inner_rows = [] for technique, subtechnique, id in sorted(techniques): if not subtechnique: - inner_rows.append("%s %s" % (rutils.bold(technique), id)) + inner_rows.append(f"{rutils.bold(technique)} {id}") else: - inner_rows.append("%s::%s %s" % (rutils.bold(technique), subtechnique, id)) + inner_rows.append(f"{rutils.bold(technique)}::{subtechnique} {id}") rows.append( ( rutils.bold(tactic.upper()), @@ -178,9 +178,9 @@ def render_mbc(doc: rd.ResultDocument, ostream: StringIO): inner_rows = [] for behavior, method, id in sorted(behaviors): if not method: - inner_rows.append("%s [%s]" % (rutils.bold(behavior), id)) + inner_rows.append(f"{rutils.bold(behavior)} [{id}]") else: - inner_rows.append("%s::%s [%s]" % (rutils.bold(behavior), method, id)) + inner_rows.append(f"{rutils.bold(behavior)}::{method} [{id}]") rows.append( ( rutils.bold(objective.upper()), diff --git a/capa/render/utils.py b/capa/render/utils.py index 50e36cca..c65b705b 100644 --- a/capa/render/utils.py +++ b/capa/render/utils.py @@ -32,7 +32,7 @@ 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) + return f"{'::'.join(data.parts)} [{data.id}]" def capability_rules(doc: rd.ResultDocument) -> Iterator[rd.RuleMatches]: diff --git a/capa/render/verbose.py b/capa/render/verbose.py index e494ff06..536e7242 100644 --- a/capa/render/verbose.py +++ b/capa/render/verbose.py @@ -121,7 +121,7 @@ def render_rules(ostream, doc: rd.ResultDocument): if count == 1: capability = rutils.bold(rule.meta.name) else: - capability = "%s (%d matches)" % (rutils.bold(rule.meta.name), count) + capability = f"{rutils.bold(rule.meta.name)} ({count} matches)" ostream.writeln(capability) had_match = True diff --git a/capa/render/vverbose.py b/capa/render/vverbose.py index c65e28e4..0c782853 100644 --- a/capa/render/vverbose.py +++ b/capa/render/vverbose.py @@ -43,7 +43,7 @@ def render_locations(ostream, locations: Iterable[frz.Address]): # 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)) + ostream.write(f", and {(len(locations) - 4)} more...") elif len(locations) > 1: ostream.write(", ".join(map(v.format_address, locations))) @@ -62,7 +62,7 @@ def render_statement(ostream, match: rd.Match, statement: rd.Statement, indent=0 ostream.write(":") if statement.description: - ostream.write(" = %s" % statement.description) + ostream.write(f" = {statement.description}") ostream.writeln("") elif isinstance(statement, (rd.CompoundStatement)): @@ -71,14 +71,14 @@ def render_statement(ostream, match: rd.Match, statement: rd.Statement, indent=0 ostream.write(":") if statement.description: - ostream.write(" = %s" % statement.description) + ostream.write(f" = {statement.description}") ostream.writeln("") elif isinstance(statement, rd.SomeStatement): - ostream.write("%d or more:" % (statement.count)) + ostream.write(f"{statement.count} or more:") if statement.description: - ostream.write(" = %s" % statement.description) + ostream.write(f" = {statement.description}") ostream.writeln("") elif isinstance(statement, rd.RangeStatement): @@ -92,28 +92,28 @@ def render_statement(ostream, match: rd.Match, statement: rd.Statement, indent=0 if value: if isinstance(child, frzf.StringFeature): - value = '"%s"' % capa.features.common.escape_string(value) + value = f'"{capa.features.common.escape_string(value)}"' value = rutils.bold2(value) if child.description: - ostream.write("count(%s(%s = %s)): " % (child.type, value, child.description)) + ostream.write(f"count({child.type}({value} = {child.description})): ") else: - ostream.write("count(%s(%s)): " % (child.type, value)) + ostream.write(f"count({child.type}({value})): ") else: - ostream.write("count(%s): " % child.type) + ostream.write(f"count({child.type}): ") if statement.max == statement.min: - ostream.write("%d" % (statement.min)) + ostream.write(f"{statement.min}") elif statement.min == 0: - ostream.write("%d or fewer" % (statement.max)) + ostream.write(f"{statement.max} or fewer") elif statement.max == (1 << 64 - 1): - ostream.write("%d or more" % (statement.min)) + ostream.write(f"{statement.min} or more") else: - ostream.write("between %d and %d" % (statement.min, statement.max)) + ostream.write(f"between {statement.min} and {statement.max}") if statement.description: - ostream.write(" = %s" % statement.description) + ostream.write(f" = {statement.description}") render_locations(ostream, match.locations) ostream.writeln("") @@ -122,7 +122,7 @@ def render_statement(ostream, match: rd.Match, statement: rd.Statement, indent=0 def render_string_value(s: str) -> str: - return '"%s"' % capa.features.common.escape_string(s) + return f'"{capa.features.common.escape_string(s)}"' def render_feature(ostream, match: rd.Match, feature: frzf.Feature, indent=0): @@ -143,7 +143,7 @@ def render_feature(ostream, match: rd.Match, feature: frzf.Feature, indent=0): value = feature.dict(by_alias=True).get(key, None) if value is None: - raise ValueError("%s contains None" % key) + raise ValueError(f"{key} contains None") if not isinstance(feature, (frzf.RegexFeature, frzf.SubstringFeature)): # like: @@ -290,11 +290,11 @@ def render_rules(ostream, doc: rd.ResultDocument): if count == 1: if rule.meta.lib: lib_info = " (library rule)" - capability = "%s%s" % (rutils.bold(rule.meta.name), lib_info) + capability = f"{rutils.bold(rule.meta.name)}{lib_info}" else: 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) + capability = f"{rutils.bold(rule.meta.name)} ({count} matches{lib_info})" ostream.writeln(capability) had_match = True @@ -345,7 +345,7 @@ def render_rules(ostream, doc: rd.ResultDocument): # 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))) + raise RuntimeError(f"unexpected file scope match count: {len(matches)}") first_address, first_match = matches[0] render_match(ostream, first_match, indent=0) else: diff --git a/capa/rules/__init__.py b/capa/rules/__init__.py index 7d98e25a..64fd7e37 100644 --- a/capa/rules/__init__.py +++ b/capa/rules/__init__.py @@ -163,7 +163,7 @@ class InvalidRule(ValueError): self.msg = msg def __str__(self): - return "invalid rule: %s" % (self.msg) + return f"invalid rule: {self.msg}" def __repr__(self): return str(self) @@ -177,7 +177,7 @@ class InvalidRuleWithPath(InvalidRule): self.__cause__ = None def __str__(self): - return "invalid rule: %s: %s" % (self.path, self.msg) + return f"invalid rule: {self.path}: {self.msg}" class InvalidRuleSet(ValueError): @@ -186,7 +186,7 @@ class InvalidRuleSet(ValueError): self.msg = msg def __str__(self): - return "invalid rule set: %s" % (self.msg) + return f"invalid rule set: {self.msg}" def __repr__(self): return str(self) @@ -200,14 +200,14 @@ def ensure_feature_valid_for_scope(scope: str, feature: Union[Feature, Statement and isinstance(feature.value, str) and capa.features.common.Characteristic(feature.value) not in SUPPORTED_FEATURES[scope] ): - raise InvalidRule("feature %s not supported for scope %s" % (feature, scope)) + raise InvalidRule(f"feature {feature} not supported for scope {scope}") if not isinstance(feature, capa.features.common.Characteristic): # features of this scope that are not Characteristics will be Type instances. # check that the given feature is one of these types. types_for_scope = filter(lambda t: isinstance(t, type), SUPPORTED_FEATURES[scope]) if not isinstance(feature, tuple(types_for_scope)): # type: ignore - raise InvalidRule("feature %s not supported for scope %s" % (feature, scope)) + raise InvalidRule(f"feature {feature} not supported for scope {scope}") def parse_int(s: str) -> int: @@ -224,10 +224,10 @@ def parse_range(s: str): """ # we want to use `{` characters, but this is a dict in yaml. if not s.startswith("("): - raise InvalidRule("invalid range: %s" % (s)) + raise InvalidRule(f"invalid range: {s}") if not s.endswith(")"): - raise InvalidRule("invalid range: %s" % (s)) + raise InvalidRule(f"invalid range: {s}") s = s[len("(") : -len(")")] min_spec, _, max_spec = s.partition(",") @@ -296,7 +296,7 @@ def parse_feature(key: str): elif key == "property": return capa.features.insn.Property else: - raise InvalidRule("unexpected statement: %s" % key) + raise InvalidRule(f"unexpected statement: {key}") # this is the separator between a feature value and its description @@ -310,11 +310,11 @@ def parse_bytes(s: str) -> bytes: try: b = codecs.decode(s.replace(" ", "").encode("ascii"), "hex") except binascii.Error: - raise InvalidRule('unexpected bytes value: must be a valid hex sequence: "%s"' % s) + raise InvalidRule(f'unexpected bytes value: must be a valid hex sequence: "{s}"') if len(b) > MAX_BYTES_FEATURE_SIZE: raise InvalidRule( - "unexpected bytes value: byte sequences must be no larger than %s bytes" % MAX_BYTES_FEATURE_SIZE + f"unexpected bytes value: byte sequences must be no larger than {MAX_BYTES_FEATURE_SIZE} bytes" ) return b @@ -337,15 +337,14 @@ def parse_description(s: Union[str, int, bytes], value_type: str, description=No # - number: 10 = CONST_FOO # description: CONST_FOO raise InvalidRule( - 'unexpected value: "%s", only one description allowed (inline description with `%s`)' - % (s, DESCRIPTION_SEPARATOR) + f'unexpected value: "{s}", only one description allowed (inline description with `{DESCRIPTION_SEPARATOR}`)' ) value, _, description = s.partition(DESCRIPTION_SEPARATOR) if description == "": # sanity check: # there is an empty description, like `number: 10 =` - raise InvalidRule('unexpected value: "%s", description cannot be empty' % s) + raise InvalidRule(f'unexpected value: "{s}", description cannot be empty') else: # this is a string, but there is no description, # like: `api: CreateFileA` @@ -372,7 +371,7 @@ def parse_description(s: Union[str, int, bytes], value_type: str, description=No try: value = parse_int(value) except ValueError: - raise InvalidRule('unexpected value: "%s", must begin with numerical value' % value) + raise InvalidRule(f'unexpected value: "{value}", must begin with numerical value') else: # the value might be a number, like: `number: 10` @@ -532,9 +531,9 @@ def build_statements(d, scope: str): min, max = parse_range(count) return ceng.Range(feature, min=min, max=max, description=description) else: - raise InvalidRule("unexpected range: %s" % (count)) + raise InvalidRule(f"unexpected range: {count}") elif key == "string" and not isinstance(d[key], str): - raise InvalidRule("ambiguous string value %s, must be defined as explicit string" % d[key]) + raise InvalidRule(f"ambiguous string value {d[key]}, must be defined as explicit string") elif key.startswith("operand[") and key.endswith("].number"): index = key[len("operand[") : -len("].number")] @@ -573,12 +572,12 @@ def build_statements(d, scope: str): or (key == "format" and d[key] not in capa.features.common.VALID_FORMAT) or (key == "arch" and d[key] not in capa.features.common.VALID_ARCH) ): - raise InvalidRule("unexpected %s value %s" % (key, d[key])) + raise InvalidRule(f"unexpected {key} value {d[key]}") elif key.startswith("property/"): access = key[len("property/") :] if access not in capa.features.common.VALID_FEATURE_ACCESS: - raise InvalidRule("unexpected %s access %s" % (key, access)) + raise InvalidRule(f"unexpected {key} access {access}") value, description = parse_description(d[key], key, d.get("description")) try: @@ -617,10 +616,10 @@ class Rule: self.definition = definition def __str__(self): - return "Rule(name=%s)" % (self.name) + return f"Rule(name={self.name})" def __repr__(self): - return "Rule(scope=%s, name=%s)" % (self.scope, self.name) + return f"Rule(scope={self.scope}, name={self.name})" def get_dependencies(self, namespaces): """ @@ -998,7 +997,7 @@ def ensure_rule_dependencies_are_met(rules: List[Rule]) -> None: for rule in rules_by_name.values(): for dep in rule.get_dependencies(namespaces): if dep not in rules_by_name: - raise InvalidRule('rule "%s" depends on missing rule "%s"' % (rule.name, dep)) + raise InvalidRule(f'rule "{rule.name}" depends on missing rule "{dep}"') def index_rules_by_namespace(rules: List[Rule]) -> Dict[str, List[Rule]]: diff --git a/scripts/bulk-process.py b/scripts/bulk-process.py index 53880bcf..f22c55e0 100644 --- a/scripts/bulk-process.py +++ b/scripts/bulk-process.py @@ -112,7 +112,7 @@ def get_capa_results(args): return { "path": path, "status": "error", - "error": "input file does not appear to be a PE file: %s" % path, + "error": f"input file does not appear to be a PE file: {path}", } except capa.main.UnsupportedRuntimeError: return { @@ -124,7 +124,7 @@ def get_capa_results(args): return { "path": path, "status": "error", - "error": "unexpected error: %s" % (e), + "error": f"unexpected error: {e}", } meta = capa.main.collect_metadata([], path, [], extractor) @@ -202,7 +202,7 @@ def main(argv=None): elif result["status"] == "ok": results[result["path"]] = rd.ResultDocument.parse_obj(result["ok"]).json(exclude_none=True) else: - raise ValueError("unexpected status: %s" % (result["status"])) + raise ValueError(f"unexpected status: {result['status']}") print(json.dumps(results)) diff --git a/scripts/capa_as_library.py b/scripts/capa_as_library.py index a91ce133..884b2e94 100644 --- a/scripts/capa_as_library.py +++ b/scripts/capa_as_library.py @@ -77,7 +77,7 @@ def render_capabilities(doc: rd.ResultDocument, result): if count == 1: capability = rule.meta.name else: - capability = "%s (%d matches)" % (rule.meta.name, count) + capability = f"{rule.meta.name} ({count} matches)" result["CAPABILITY"].setdefault(rule.meta.namespace, list()) result["CAPABILITY"][rule.meta.namespace].append(capability) @@ -108,9 +108,9 @@ def render_attack(doc, result): inner_rows = [] for technique, subtechnique, id in sorted(techniques): if subtechnique is None: - inner_rows.append("%s %s" % (technique, id)) + inner_rows.append(f"{technique} {id}") else: - inner_rows.append("%s::%s %s" % (technique, subtechnique, id)) + inner_rows.append(f"{technique}::{subtechnique} {id}") result["ATTCK"].setdefault(tactic.upper(), inner_rows) @@ -142,9 +142,9 @@ def render_mbc(doc, result): inner_rows = [] for behavior, method, id in sorted(behaviors): if method is None: - inner_rows.append("%s [%s]" % (behavior, id)) + inner_rows.append(f"{behavior} [{id}]") else: - inner_rows.append("%s::%s [%s]" % (behavior, method, id)) + inner_rows.append(f"{behavior}::{method} [{id}]") result["MBC"].setdefault(objective.upper(), inner_rows) diff --git a/scripts/import-to-bn.py b/scripts/import-to-bn.py index d157af44..1e5b4ca0 100644 --- a/scripts/import-to-bn.py +++ b/scripts/import-to-bn.py @@ -57,7 +57,7 @@ def load_analysis(bv): if not path or not os.access(path, os.R_OK): binaryninja.log_error("Invalid filename.") return 0 - binaryninja.log_info("Using capa file %s" % path) + binaryninja.log_info(f"Using capa file {path}") with open(path, "rb") as f: doc = json.loads(f.read().decode("utf-8")) @@ -97,7 +97,7 @@ def load_analysis(bv): else: cmt = f"{name}" - binaryninja.log_info("0x%x: %s" % (va, cmt)) + binaryninja.log_info(f"{hex(va)}: {cmt}") try: # message will look something like: # diff --git a/scripts/import-to-ida.py b/scripts/import-to-ida.py index a32b45b9..058c2553 100644 --- a/scripts/import-to-ida.py +++ b/scripts/import-to-ida.py @@ -101,9 +101,9 @@ def main(): rows = sorted(rows) for ns, name, va in rows: if ns: - cmt = f"{name} ({ns})" + cmt = name + f"({ns})" else: - cmt = f"{name}" + cmt = name logger.info("0x%x: %s", va, cmt) try: diff --git a/scripts/profile-memory.py b/scripts/profile-memory.py index c6e2df90..e5bc6515 100644 --- a/scripts/profile-memory.py +++ b/scripts/profile-memory.py @@ -19,17 +19,17 @@ def display_top(snapshot, key_type="lineno", limit=10): print(f"Top {limit} lines") for index, stat in enumerate(top_stats[:limit], 1): frame = stat.traceback[0] - print(f"#{index}: {frame.filename}:{frame.lineno}: {stat.size / 1024:.1f} KiB") + print(f"#{index}: {frame.filename}:{frame.lineno}: {(stat.size/1024):.1f} KiB") line = linecache.getline(frame.filename, frame.lineno).strip() if line: - print(" %s" % line) + print(f" {line}") other = top_stats[limit:] if other: size = sum(stat.size for stat in other) - print(f"{len(other)} other: {size / 1024:.1f} KiB") + print(f"{len(other)} other: {(size/1024):.1f} KiB") total = sum(stat.size for stat in top_stats) - print(f"Total allocated size: {total / 1024:.1f} KiB") + print(f"Total allocated size: {(total/1024):.1f} KiB") def main(): @@ -49,7 +49,7 @@ def main(): print() for i in range(count): - print(f"iteration {i + 1}/{count}...") + print(f"iteration {i+1}/{count}...") with contextlib.redirect_stdout(io.StringIO()): with contextlib.redirect_stderr(io.StringIO()): t0 = time.time() @@ -59,9 +59,9 @@ def main(): gc.collect() process = psutil.Process(os.getpid()) - print(f" duration: {t1 - t0:.02f}s") - print(f" rss: {process.memory_info().rss / 1024 / 1024:.1f} MiB") - print(f" vms: {process.memory_info().vms / 1024 / 1024:.1f} MiB") + print(f" duration: {(t1-t0):.2f}") + print(f" rss: {(process.memory_info().rss / 1024 / 1024):.1f} MiB") + print(f" vms: {(process.memory_info().vms / 1024 / 1024):.1f} MiB") print("done.") gc.collect() diff --git a/scripts/profile-time.py b/scripts/profile-time.py index 09731f89..0c7f0783 100644 --- a/scripts/profile-time.py +++ b/scripts/profile-time.py @@ -133,9 +133,9 @@ def main(argv=None): # so lets put that first. # # https://docs.python.org/3/library/timeit.html#timeit.Timer.repeat - "%0.2fs" % (min(samples) / float(args.number)), - "%0.2fs" % (sum(samples) / float(args.repeat) / float(args.number)), - "%0.2fs" % (max(samples) / float(args.number)), + f"{(min(samples) / float(args.number)):.2f}s", + f"{(sum(samples) / float(args.repeat) / float(args.number)):.2f}s", + f"{(max(samples) / float(args.number)):.2f}s", ) ], headers=["label", "count(evaluations)", "min(time)", "avg(time)", "max(time)"], diff --git a/scripts/show-capabilities-by-function.py b/scripts/show-capabilities-by-function.py index 8045e895..dbd47f8f 100644 --- a/scripts/show-capabilities-by-function.py +++ b/scripts/show-capabilities-by-function.py @@ -118,7 +118,7 @@ def render_matches_by_function(doc: rd.ResultDocument): for f in doc.meta.analysis.feature_counts.functions: if not matches_by_function.get(f.address, {}): continue - ostream.writeln("function at %s with %d features: " % (capa.render.verbose.format_address(addr), f.count)) + ostream.writeln(f"function at {capa.render.verbose.format_address(addr)} with {f.count} features: ") for rule_name in sorted(matches_by_function[f.address]): ostream.writeln(" - " + rule_name) diff --git a/scripts/show-features.py b/scripts/show-features.py index d23a9a0a..297977d5 100644 --- a/scripts/show-features.py +++ b/scripts/show-features.py @@ -130,11 +130,11 @@ def main(argv=None): return -1 for feature, addr in extractor.extract_global_features(): - print("global: %s: %s" % (format_address(addr), feature)) + print(f"global: {format_address(addr)}: {feature}") if not args.function: for feature, addr in extractor.extract_file_features(): - print("file: %s: %s" % (format_address(addr), feature)) + print(f"file: {format_address(addr)}: {feature}") function_handles = tuple(extractor.get_functions()) @@ -146,11 +146,11 @@ def main(argv=None): function_handles = tuple(filter(lambda fh: format_address(fh.address) == args.function, function_handles)) if args.function not in [format_address(fh.address) for fh in function_handles]: - print("%s not a function" % args.function) + print(f"{args.function} not a function") return -1 if len(function_handles) == 0: - print("%s not a function", args.function) + print(f"{args.function} not a function") return -1 print_features(function_handles, extractor) @@ -164,13 +164,13 @@ def ida_main(): import capa.features.extractors.ida.extractor function = idc.get_func_attr(idc.here(), idc.FUNCATTR_START) - print("getting features for current function 0x%X" % function) + print(f"getting features for current function {hex(function)}") extractor = capa.features.extractors.ida.extractor.IdaFeatureExtractor() if not function: for feature, addr in extractor.extract_file_features(): - print("file: %s: %s" % (format_address(addr), feature)) + print(f"file: {format_address(addr)}: {feature}") return function_handles = tuple(extractor.get_functions()) @@ -179,7 +179,7 @@ def ida_main(): function_handles = tuple(filter(lambda fh: fh.inner.start_ea == function, function_handles)) if len(function_handles) == 0: - print("0x%X not a function" % function) + print(f"{hex(function)} not a function") return -1 print_features(function_handles, extractor) @@ -194,16 +194,16 @@ def print_features(functions, extractor: capa.features.extractors.base_extractor logger.debug("skipping library function %s (%s)", format_address(f.address), function_name) continue - print("func: %s" % (format_address(f.address))) + print(f"func: {format_address(f.address)}") for feature, addr in extractor.extract_function_features(f): if capa.features.common.is_global_feature(feature): continue if f.address != addr: - print(" func: %s: %s -> %s" % (format_address(f.address), feature, format_address(addr))) + print(f" func: {format_address(f.address)}: {feature} -> {format_address(addr)}") else: - print(" func: %s: %s" % (format_address(f.address), feature)) + print(f" func: {format_address(f.address)}: {feature}") for bb in extractor.get_basic_blocks(f): for feature, addr in extractor.extract_basic_block_features(f, bb): @@ -211,9 +211,9 @@ def print_features(functions, extractor: capa.features.extractors.base_extractor continue if bb.address != addr: - print(" bb: %s: %s -> %s" % (format_address(bb.address), feature, format_address(addr))) + print(f" bb: {format_address(bb.address)}: {feature} -> {format_address(addr)}") else: - print(" bb: %s: %s" % (format_address(bb.address), feature)) + print(f" bb: {format_address(bb.address)}: {feature}") for insn in extractor.get_instructions(f, bb): for feature, addr in extractor.extract_insn_features(f, bb, insn): @@ -223,16 +223,10 @@ def print_features(functions, extractor: capa.features.extractors.base_extractor try: if insn.address != addr: print( - " insn: %s: %s: %s -> %s" - % ( - format_address(f.address), - format_address(insn.address), - feature, - format_address(addr), - ) + f" insn: {format_address(f.address)}: {format_address(insn.address)}: {feature} -> {format_address(addr)}" ) else: - print(" insn: %s: %s" % (format_address(insn.address), feature)) + print(f" insn: {format_address(insn.address)}: {feature}") except UnicodeEncodeError: # may be an issue while piping to less and encountering non-ascii characters diff --git a/tests/fixtures.py b/tests/fixtures.py index bfbbbdb3..5602f096 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -283,7 +283,7 @@ def get_data_path_by_name(name): elif name.startswith("294b8d"): return os.path.join(CD, "data", "294b8db1f2702b60fb2e42fdc50c2cee6a5046112da9a5703a548a4fa50477bc.elf_") else: - raise ValueError("unexpected sample fixture: %s" % name) + raise ValueError(f"unexpected sample fixture: {name}") def get_sample_md5_by_name(name): @@ -341,7 +341,7 @@ def get_sample_md5_by_name(name): # file name is SHA256 hash return "3db3e55b16a7b1b1afb970d5e77c5d98" else: - raise ValueError("unexpected sample fixture: %s" % name) + raise ValueError(f"unexpected sample fixture: {name}") def resolve_sample(sample): @@ -981,21 +981,16 @@ def do_test_feature_presence(get_extractor, sample, scope, feature, expected): extractor = get_extractor(sample) features = scope(extractor) if expected: - msg = "%s should be found in %s" % (str(feature), scope.__name__) + msg = f"{str(feature)} should be found in {scope.__name__}" else: - msg = "%s should not be found in %s" % (str(feature), scope.__name__) + msg = f"{str(feature)} should not be found in {scope.__name__}" assert feature.evaluate(features) == expected, msg def do_test_feature_count(get_extractor, sample, scope, feature, expected): extractor = get_extractor(sample) features = scope(extractor) - msg = "%s should be found %d times in %s, found: %d" % ( - str(feature), - expected, - scope.__name__, - len(features[feature]), - ) + msg = f"{str(feature)} should be found {expected} times in {scope.__name__}, found: {len(features[feature])}" assert len(features[feature]) == expected, msg diff --git a/tests/test_ida_features.py b/tests/test_ida_features.py index b408d632..b6917262 100644 --- a/tests/test_ida_features.py +++ b/tests/test_ida_features.py @@ -31,7 +31,7 @@ def check_input_file(wanted): found = binascii.hexlify(idautils.GetInputFileMD5()[:15]).decode("ascii").lower() if not wanted.startswith(found): - raise RuntimeError("please run the tests against sample with MD5: `%s`" % (wanted)) + raise RuntimeError(f"please run the tests against sample with MD5: `{wanted}`") def get_ida_extractor(_path): @@ -51,7 +51,7 @@ def test_ida_features(): try: check_input_file(fixtures.get_sample_md5_by_name(sample)) except RuntimeError: - print("SKIP %s" % (id)) + print(f"SKIP {id}") continue scope = fixtures.resolve_scope(scope) @@ -60,10 +60,10 @@ def test_ida_features(): try: fixtures.do_test_feature_presence(get_ida_extractor, sample, scope, feature, expected) except Exception as e: - print("FAIL %s" % (id)) + print(f"FAIL {id}") traceback.print_exc() else: - print("OK %s" % (id)) + print(f"OK {id}") @pytest.mark.skip(reason="IDA Pro tests must be run within IDA") @@ -74,7 +74,7 @@ def test_ida_feature_counts(): try: check_input_file(fixtures.get_sample_md5_by_name(sample)) except RuntimeError: - print("SKIP %s" % (id)) + print(f"SKIP {id}") continue scope = fixtures.resolve_scope(scope) @@ -83,10 +83,10 @@ def test_ida_feature_counts(): try: fixtures.do_test_feature_count(get_ida_extractor, sample, scope, feature, expected) except Exception as e: - print("FAIL %s" % (id)) + print(f"FAIL {id}") traceback.print_exc() else: - print("OK %s" % (id)) + print(f"OK {id}") if __name__ == "__main__": diff --git a/tests/test_scripts.py b/tests/test_scripts.py index f5a9d701..f48a6f99 100644 --- a/tests/test_scripts.py +++ b/tests/test_scripts.py @@ -62,5 +62,5 @@ def test_bulk_process(tmpdir): def run_program(script_path, args): args = [sys.executable] + [script_path] + args - print("running: '%s'" % args) + print(f"running: '{args}'") return subprocess.run(args) From d5937e4af54f7fea4b83104ed63cc842a5e7f48a Mon Sep 17 00:00:00 2001 From: Capa Bot Date: Thu, 16 Mar 2023 17:41:19 +0000 Subject: [PATCH 45/49] Sync capa rules submodule --- rules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules b/rules index 6bd733a2..83103555 160000 --- a/rules +++ b/rules @@ -1 +1 @@ -Subproject commit 6bd733a2e38b7199ec0586ad6ff30288e7a426d4 +Subproject commit 83103555285ea92d4b215118a9eec3ab94cb0343 From 1746a640ccba79557cab36a170436ec586c7c391 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Mar 2023 14:12:20 +0100 Subject: [PATCH 46/49] build(deps): bump pydantic from 1.10.5 to 1.10.6 (#1380) Bumps [pydantic](https://github.com/pydantic/pydantic) from 1.10.5 to 1.10.6. - [Release notes](https://github.com/pydantic/pydantic/releases) - [Changelog](https://github.com/pydantic/pydantic/blob/v1.10.6/HISTORY.md) - [Commits](https://github.com/pydantic/pydantic/compare/v1.10.5...v1.10.6) --- updated-dependencies: - dependency-name: pydantic dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 97a8ff54..ce6aa5d6 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ requirements = [ "pyelftools==0.29", "dnfile==0.13.0", "dncil==1.0.2", - "pydantic==1.10.5", + "pydantic==1.10.6", ] # this sets __version__ From 008f6d1839ee404a5698be27e268b8814d0a1e98 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Mar 2023 14:58:43 +0000 Subject: [PATCH 47/49] build(deps-dev): bump pyinstaller from 5.8.0 to 5.9.0 Bumps [pyinstaller](https://github.com/pyinstaller/pyinstaller) from 5.8.0 to 5.9.0. - [Release notes](https://github.com/pyinstaller/pyinstaller/releases) - [Changelog](https://github.com/pyinstaller/pyinstaller/blob/develop/doc/CHANGES.rst) - [Commits](https://github.com/pyinstaller/pyinstaller/compare/v5.8.0...v5.9.0) --- updated-dependencies: - dependency-name: pyinstaller dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index ce6aa5d6..5b02288b 100644 --- a/setup.py +++ b/setup.py @@ -89,7 +89,7 @@ setuptools.setup( "types_requests==2.28.1", ], "build": [ - "pyinstaller==5.8.0", + "pyinstaller==5.9.0", ], }, zip_safe=False, From b0975696077fd55505f3a378af959e7cee372132 Mon Sep 17 00:00:00 2001 From: AG <98327736+ggold7046@users.noreply.github.com> Date: Tue, 21 Mar 2023 19:53:10 +0530 Subject: [PATCH 48/49] Update view.py Updated with f string for better readability. --- capa/ida/plugin/view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/capa/ida/plugin/view.py b/capa/ida/plugin/view.py index 93217028..4dba4ca1 100644 --- a/capa/ida/plugin/view.py +++ b/capa/ida/plugin/view.py @@ -93,7 +93,7 @@ def parse_node_for_feature(feature, description, comment, depth): if name in ("string",): display += f"{' '*depth}{feature}" if comment: - display += " # %s" % comment + display += f" # {comment}" display += f"\n{' '*(depth+2)}description: {description}\n" else: display += f"{' '*depth}- count({name}({value} = {description})): {count}" From 03996f2b82e1a1a0273e715a81f34d61441e2bda Mon Sep 17 00:00:00 2001 From: Capa Bot Date: Tue, 21 Mar 2023 21:04:25 +0000 Subject: [PATCH 49/49] Sync capa rules submodule --- rules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules b/rules index 83103555..aa2dc113 160000 --- a/rules +++ b/rules @@ -1 +1 @@ -Subproject commit 83103555285ea92d4b215118a9eec3ab94cb0343 +Subproject commit aa2dc1137dca05215f71a48926c56345cc462173