yaralyzer 1.0.5__tar.gz → 1.0.7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of yaralyzer might be problematic. Click here for more details.

Files changed (30) hide show
  1. {yaralyzer-1.0.5 → yaralyzer-1.0.7}/CHANGELOG.md +8 -0
  2. {yaralyzer-1.0.5 → yaralyzer-1.0.7}/PKG-INFO +7 -2
  3. {yaralyzer-1.0.5 → yaralyzer-1.0.7}/pyproject.toml +43 -17
  4. {yaralyzer-1.0.5 → yaralyzer-1.0.7}/yaralyzer/__init__.py +0 -2
  5. {yaralyzer-1.0.5 → yaralyzer-1.0.7}/yaralyzer/bytes_match.py +35 -33
  6. {yaralyzer-1.0.5 → yaralyzer-1.0.7}/yaralyzer/config.py +1 -1
  7. {yaralyzer-1.0.5 → yaralyzer-1.0.7}/yaralyzer/decoding/bytes_decoder.py +3 -6
  8. {yaralyzer-1.0.5 → yaralyzer-1.0.7}/yaralyzer/decoding/decoding_attempt.py +4 -3
  9. {yaralyzer-1.0.5 → yaralyzer-1.0.7}/yaralyzer/encoding_detection/character_encodings.py +38 -39
  10. {yaralyzer-1.0.5 → yaralyzer-1.0.7}/yaralyzer/encoding_detection/encoding_assessment.py +2 -2
  11. {yaralyzer-1.0.5 → yaralyzer-1.0.7}/yaralyzer/encoding_detection/encoding_detector.py +3 -4
  12. {yaralyzer-1.0.5 → yaralyzer-1.0.7}/yaralyzer/helpers/bytes_helper.py +4 -4
  13. {yaralyzer-1.0.5 → yaralyzer-1.0.7}/yaralyzer/helpers/dict_helper.py +0 -1
  14. {yaralyzer-1.0.5 → yaralyzer-1.0.7}/yaralyzer/helpers/list_helper.py +1 -0
  15. {yaralyzer-1.0.5 → yaralyzer-1.0.7}/yaralyzer/helpers/rich_text_helper.py +7 -7
  16. {yaralyzer-1.0.5 → yaralyzer-1.0.7}/yaralyzer/output/file_export.py +1 -1
  17. {yaralyzer-1.0.5 → yaralyzer-1.0.7}/yaralyzer/output/file_hashes_table.py +4 -4
  18. {yaralyzer-1.0.5 → yaralyzer-1.0.7}/yaralyzer/output/rich_console.py +1 -1
  19. {yaralyzer-1.0.5 → yaralyzer-1.0.7}/yaralyzer/util/argument_parser.py +10 -10
  20. {yaralyzer-1.0.5 → yaralyzer-1.0.7}/yaralyzer/util/logging.py +1 -1
  21. {yaralyzer-1.0.5 → yaralyzer-1.0.7}/yaralyzer/yara/yara_match.py +1 -1
  22. {yaralyzer-1.0.5 → yaralyzer-1.0.7}/yaralyzer/yara/yara_rule_builder.py +16 -17
  23. {yaralyzer-1.0.5 → yaralyzer-1.0.7}/yaralyzer/yaralyzer.py +27 -28
  24. {yaralyzer-1.0.5 → yaralyzer-1.0.7}/.yaralyzer.example +0 -0
  25. {yaralyzer-1.0.5 → yaralyzer-1.0.7}/LICENSE +0 -0
  26. {yaralyzer-1.0.5 → yaralyzer-1.0.7}/README.md +0 -0
  27. {yaralyzer-1.0.5 → yaralyzer-1.0.7}/yaralyzer/helpers/file_helper.py +0 -0
  28. {yaralyzer-1.0.5 → yaralyzer-1.0.7}/yaralyzer/helpers/string_helper.py +0 -0
  29. {yaralyzer-1.0.5 → yaralyzer-1.0.7}/yaralyzer/output/decoding_attempts_table.py +0 -0
  30. {yaralyzer-1.0.5 → yaralyzer-1.0.7}/yaralyzer/output/regex_match_metrics.py +0 -0
@@ -1,5 +1,13 @@
1
1
  # NEXT RELEASE
2
2
 
3
+ ### 1.0.7
4
+ * Add `Changelog` to PyPi URLs, add some more PyPi classifiers
5
+ * Add `.flake8` config file and fix style errors
6
+
7
+ ### 1.0.6
8
+ * Add `Environment :: Console` and `Programming Language :: Python` to PyPi classifiers
9
+ * Add `LICENSE` to PyPi package
10
+
3
11
  ### 1.0.5
4
12
  * Add `Development Status :: 5 - Production/Stable` to pypi classifiers
5
13
 
@@ -1,20 +1,24 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: yaralyzer
3
- Version: 1.0.5
3
+ Version: 1.0.7
4
4
  Summary: Visualize and force decode YARA and regex matches found in a file or byte stream. With colors. Lots of colors.
5
5
  Home-page: https://github.com/michelcrypt4d4mus/yaralyzer
6
6
  License: GPL-3.0-or-later
7
- Keywords: ascii art,binary,character encoding,color,cybersecurity,data visualization,decode,DFIR,encoding,infosec,maldoc,malicious,malware,malware analysis,regex,regular expressions,reverse engineering,reversing,security,threat assessment,threat hunting,threat intelligence,threat research,visualization,yara
7
+ Keywords: ascii art,binary,character encoding,color,cybersecurity,data visualization,decode,DFIR,encoding,infosec,maldoc,malicious,malware,malware analysis,regex,regular expressions,reverse engineering,reversing,security,threat assessment,threat hunting,threat intelligence,threat research,threatintel,visualization,yara
8
8
  Author: Michel de Cryptadamus
9
9
  Author-email: michel@cryptadamus.com
10
10
  Requires-Python: >=3.9,<4.0
11
11
  Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Environment :: Console
12
13
  Classifier: Intended Audience :: Information Technology
13
14
  Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
15
+ Classifier: Programming Language :: Python
14
16
  Classifier: Programming Language :: Python :: 3
15
17
  Classifier: Programming Language :: Python :: 3.9
16
18
  Classifier: Programming Language :: Python :: 3.10
17
19
  Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
18
22
  Classifier: Topic :: Artistic Software
19
23
  Classifier: Topic :: Scientific/Engineering :: Visualization
20
24
  Classifier: Topic :: Security
@@ -23,6 +27,7 @@ Requires-Dist: python-dotenv (>=0.21.0,<0.22.0)
23
27
  Requires-Dist: rich (>=14.1.0,<15.0.0)
24
28
  Requires-Dist: rich-argparse-plus (>=0.3.1,<0.4.0)
25
29
  Requires-Dist: yara-python (>=4.5.4,<5.0.0)
30
+ Project-URL: Changelog, https://github.com/michelcrypt4d4mus/yaralyzer/blob/master/CHANGELOG.md
26
31
  Project-URL: Documentation, https://github.com/michelcrypt4d4mus/yaralyzer
27
32
  Project-URL: Repository, https://github.com/michelcrypt4d4mus/yaralyzer
28
33
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "yaralyzer"
3
- version = "1.0.5"
3
+ version = "1.0.7"
4
4
  description = "Visualize and force decode YARA and regex matches found in a file or byte stream. With colors. Lots of colors."
5
5
  authors = ["Michel de Cryptadamus <michel@cryptadamus.com>"]
6
6
  readme = "README.md"
@@ -9,6 +9,28 @@ homepage = "https://github.com/michelcrypt4d4mus/yaralyzer"
9
9
  repository = "https://github.com/michelcrypt4d4mus/yaralyzer"
10
10
  documentation = "https://github.com/michelcrypt4d4mus/yaralyzer"
11
11
 
12
+ classifiers = [
13
+ "Development Status :: 5 - Production/Stable",
14
+ "Environment :: Console",
15
+ "Intended Audience :: Information Technology",
16
+ "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
17
+ "Programming Language :: Python",
18
+ "Programming Language :: Python :: 3.9",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3.13",
23
+ "Topic :: Artistic Software",
24
+ "Topic :: Security",
25
+ "Topic :: Scientific/Engineering :: Visualization",
26
+ ]
27
+
28
+ include = [
29
+ "CHANGELOG.md",
30
+ "LICENSE",
31
+ ".yaralyzer.example"
32
+ ]
33
+
12
34
  keywords = [
13
35
  "ascii art",
14
36
  "binary",
@@ -33,43 +55,47 @@ keywords = [
33
55
  "threat hunting",
34
56
  "threat intelligence",
35
57
  "threat research",
58
+ "threatintel",
36
59
  "visualization",
37
60
  "yara",
38
61
  ]
39
62
 
40
- classifiers = [
41
- "Development Status :: 5 - Production/Stable",
42
- "Intended Audience :: Information Technology",
43
- "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
44
- "Topic :: Artistic Software",
45
- "Topic :: Security",
46
- "Topic :: Scientific/Engineering :: Visualization",
47
- ]
48
-
49
- include = [
50
- "CHANGELOG.md",
51
- ".yaralyzer.example"
52
- ]
53
-
54
63
 
64
+ #####################
65
+ # Dependencies #
66
+ #####################
55
67
  [tool.poetry.dependencies]
56
68
  python = "^3.9"
57
69
  chardet = ">=5.0.0,<6.0.0"
58
- #plyara = "^2.1.1"
59
70
  python-dotenv = "^0.21.0"
60
71
  rich = "^14.1.0"
61
72
  rich-argparse-plus = "^0.3.1"
62
73
  yara-python = "^4.5.4"
74
+ #plyara = "^2.1.1" # TODO: use plyara for YARA rule parsing and validation
63
75
 
64
76
  [tool.poetry.group.dev.dependencies]
77
+ flake8 = "^7.3.0"
65
78
  pytest = "^7.1.3"
66
79
 
67
80
 
81
+ #############
82
+ # Scripts #
83
+ #############
68
84
  [tool.poetry.scripts]
69
85
  yaralyze = 'yaralyzer:yaralyze'
70
86
  yaralyzer_show_color_theme = 'yaralyzer.helpers.rich_text_helper:yaralyzer_show_color_theme'
71
87
 
72
88
 
89
+ #####################
90
+ # PyPi URLs #
91
+ #####################
92
+ [tool.poetry.urls]
93
+ Changelog = "https://github.com/michelcrypt4d4mus/yaralyzer/blob/master/CHANGELOG.md"
94
+
95
+
96
+ ###############################
97
+ # Poetry build system #
98
+ ###############################
73
99
  [build-system]
74
- requires = ["poetry-core"]
75
100
  build-backend = "poetry.core.masonry.api"
101
+ requires = ["poetry-core"]
@@ -11,11 +11,9 @@ if not environ.get('INVOKED_BY_PYTEST', False):
11
11
  load_dotenv(dotenv_path=dotenv_file)
12
12
  break
13
13
 
14
- from yaralyzer.config import YaralyzerConfig
15
14
  from yaralyzer.output.file_export import export_json, invoke_rich_export
16
15
  from yaralyzer.output.rich_console import console
17
16
  from yaralyzer.util.argument_parser import get_export_basepath, parse_arguments
18
- from yaralyzer.util.logging import log
19
17
  from yaralyzer.yara.yara_rule_builder import HEX, REGEX
20
18
  from yaralyzer.yaralyzer import Yaralyzer
21
19
 
@@ -13,22 +13,22 @@ from rich.text import Text
13
13
  from yara import StringMatch, StringMatchInstance
14
14
 
15
15
  from yaralyzer.config import YaralyzerConfig
16
- from yaralyzer.helpers.rich_text_helper import prefix_with_plain_text_obj
16
+ from yaralyzer.helpers.rich_text_helper import prefix_with_style
17
17
  from yaralyzer.output.file_hashes_table import bytes_hashes_table
18
18
  from yaralyzer.output.rich_console import ALERT_STYLE, GREY_ADDRESS
19
19
 
20
20
 
21
21
  class BytesMatch:
22
22
  def __init__(
23
- self,
24
- matched_against: bytes,
25
- start_idx: int,
26
- length: int,
27
- label: str,
28
- ordinal: int,
29
- match: Optional[re.Match] = None, # It's rough to get the regex from yara :(
30
- highlight_style: str = YaralyzerConfig.HIGHLIGHT_STYLE
31
- ) -> None:
23
+ self,
24
+ matched_against: bytes,
25
+ start_idx: int,
26
+ length: int,
27
+ label: str,
28
+ ordinal: int,
29
+ match: Optional[re.Match] = None, # It's rough to get the regex from yara :(
30
+ highlight_style: str = YaralyzerConfig.HIGHLIGHT_STYLE
31
+ ) -> None:
32
32
  """
33
33
  Ordinal means it's the Nth match with this regex (not super important but useful)
34
34
  YARA makes it a little rouch to get the actual regex that matched. Can be done with plyara eventually.
@@ -52,24 +52,24 @@ class BytesMatch:
52
52
 
53
53
  @classmethod
54
54
  def from_regex_match(
55
- cls,
56
- matched_against: bytes,
57
- match: re.Match,
58
- ordinal: int,
59
- highlight_style: str = YaralyzerConfig.HIGHLIGHT_STYLE
60
- ) -> 'BytesMatch':
55
+ cls,
56
+ matched_against: bytes,
57
+ match: re.Match,
58
+ ordinal: int,
59
+ highlight_style: str = YaralyzerConfig.HIGHLIGHT_STYLE
60
+ ) -> 'BytesMatch':
61
61
  return cls(matched_against, match.start(), len(match[0]), match.re.pattern, ordinal, match, highlight_style)
62
62
 
63
63
  @classmethod
64
64
  def from_yara_str(
65
- cls,
66
- matched_against: bytes,
67
- rule_name: str,
68
- yara_str_match: StringMatch,
69
- yara_str_match_instance: StringMatchInstance,
70
- ordinal: int,
71
- highlight_style: str = YaralyzerConfig.HIGHLIGHT_STYLE
72
- ) -> 'BytesMatch':
65
+ cls,
66
+ matched_against: bytes,
67
+ rule_name: str,
68
+ yara_str_match: StringMatch,
69
+ yara_str_match_instance: StringMatchInstance,
70
+ ordinal: int,
71
+ highlight_style: str = YaralyzerConfig.HIGHLIGHT_STYLE
72
+ ) -> 'BytesMatch':
73
73
  """Build a BytesMatch from a yara string match. 'matched_against' is the set of bytes yara was run against."""
74
74
  pattern_label = yara_str_match.identifier
75
75
 
@@ -89,11 +89,11 @@ class BytesMatch:
89
89
 
90
90
  @classmethod
91
91
  def from_yara_match(
92
- cls,
93
- matched_against: bytes,
94
- yara_match: dict,
95
- highlight_style: str = YaralyzerConfig.HIGHLIGHT_STYLE
96
- ) -> Iterator['BytesMatch']:
92
+ cls,
93
+ matched_against: bytes,
94
+ yara_match: dict,
95
+ highlight_style: str = YaralyzerConfig.HIGHLIGHT_STYLE
96
+ ) -> Iterator['BytesMatch']:
97
97
  """Iterator w/a BytesMatch for each string returned as part of a YARA match result dict."""
98
98
  i = 0 # For numbered labeling
99
99
 
@@ -102,13 +102,14 @@ class BytesMatch:
102
102
  for yara_str_match_instance in yara_str_match.instances:
103
103
  i += 1
104
104
 
105
- yield(cls.from_yara_str(
105
+ yield cls.from_yara_str(
106
106
  matched_against,
107
107
  yara_match['rule'],
108
108
  yara_str_match,
109
109
  yara_str_match_instance,
110
110
  i,
111
- highlight_style))
111
+ highlight_style
112
+ )
112
113
 
113
114
  def style_at_position(self, idx) -> str:
114
115
  """Get the style for the byte at position idx within the matched bytes"""
@@ -119,11 +120,12 @@ class BytesMatch:
119
120
 
120
121
  def location(self) -> Text:
121
122
  """Returns a Text obj like '(start idx: 348190, end idx: 348228)'"""
122
- location_txt = prefix_with_plain_text_obj(
123
+ location_txt = prefix_with_style(
123
124
  f"(start idx: ",
124
125
  style='off_white',
125
126
  root_style='decode.subheading'
126
127
  )
128
+
127
129
  location_txt.append(str(self.start_idx), style='number')
128
130
  location_txt.append(', end idx: ', style='off_white')
129
131
  location_txt.append(str(self.end_idx), style='number')
@@ -184,7 +186,7 @@ class BytesMatch:
184
186
  self.surrounding_bytes: bytes = self.matched_against[self.surrounding_start_idx:self.surrounding_end_idx]
185
187
 
186
188
  def __rich__(self) -> Text:
187
- headline = prefix_with_plain_text_obj(str(self.match_length), style='number', root_style='decode.subheading')
189
+ headline = prefix_with_style(str(self.match_length), style='number', root_style='decode.subheading')
188
190
  headline.append(f" bytes matching ")
189
191
  headline.append(f"{self.label} ", style=ALERT_STYLE if self.highlight_style == ALERT_STYLE else 'regex')
190
192
  headline.append('at ')
@@ -91,7 +91,7 @@ class YaralyzerConfig:
91
91
  env_var = f"{YARALYZER}_{option.upper()}"
92
92
  env_value = environ.get(env_var)
93
93
  default_value = cls.get_default_arg(option)
94
- #print(f"option: {option}, arg_value: {arg_value}, env_var: {env_var}, env_value: {env_value}, default: {default_value}")
94
+ # print(f"option: {option}, arg_value: {arg_value}, env_var: {env_var}, env_value: {env_value}, default: {default_value}") # noqa: E501
95
95
 
96
96
  # TODO: as is you can't override env vars with CLI args
97
97
  if isinstance(arg_value, bool):
@@ -3,7 +3,6 @@ Class to handle attempting to decode a chunk of bytes into strings with various
3
3
  Leverages the chardet library to both guide what encodings are attempted as well as to rank decodings
4
4
  in the results.
5
5
  """
6
-
7
6
  from collections import defaultdict
8
7
  from copy import deepcopy
9
8
  from operator import attrgetter
@@ -15,14 +14,13 @@ from rich.panel import Panel
15
14
  from rich.table import Table
16
15
  from rich.text import Text
17
16
 
18
- #from yaralyzer.bytes_match import BytesMatch
17
+ from yaralyzer.bytes_match import BytesMatch # Used to cause circular import issues
19
18
  from yaralyzer.config import YaralyzerConfig
20
19
  from yaralyzer.decoding.decoding_attempt import DecodingAttempt
21
- from yaralyzer.encoding_detection.character_encodings import ENCODING, ENCODINGS_TO_ATTEMPT, encoding_offsets
20
+ from yaralyzer.encoding_detection.character_encodings import ENCODING, ENCODINGS_TO_ATTEMPT
22
21
  from yaralyzer.encoding_detection.encoding_assessment import EncodingAssessment
23
22
  from yaralyzer.encoding_detection.encoding_detector import EncodingDetector
24
23
  from yaralyzer.helpers.dict_helper import get_dict_key_by_value
25
- from yaralyzer.helpers.list_helper import flatten
26
24
  from yaralyzer.helpers.rich_text_helper import CENTER, DECODING_ERRORS_MSG, NO_DECODING_ERRORS_MSG
27
25
  from yaralyzer.output.decoding_attempts_table import (DecodingTableRow, assessment_only_row,
28
26
  decoding_table_row, new_decoding_attempts_table)
@@ -66,7 +64,6 @@ class BytesDecoder:
66
64
  if self.bytes_match.is_decodable():
67
65
  yield self._build_decodings_table()
68
66
  elif YaralyzerConfig.args.standalone_mode:
69
- # TODO: yield self.bytes_match.suppression_notice() (i guess to show some notice that things are suppressed?)
70
67
  yield self._build_decodings_table(True)
71
68
 
72
69
  yield NewLine()
@@ -136,7 +133,7 @@ class BytesDecoder:
136
133
  # If the decoding can have a start offset add an appropriate extension to the encoding label
137
134
  if decoding.start_offset_label:
138
135
  if assessment.language:
139
- log.warning(f"{decoding.encoding} has offset {decoding.start_offset} and language '{assessment.language}'")
136
+ log.warning(f"{decoding.encoding} offset {decoding.start_offset} AND language '{assessment.language}'")
140
137
  else:
141
138
  assessment = deepcopy(assessment)
142
139
  assessment.set_encoding_label(decoding.start_offset_label)
@@ -7,10 +7,11 @@ from typing import Optional
7
7
  from rich.markup import escape
8
8
  from rich.text import Text
9
9
 
10
+ from yaralyzer.bytes_match import BytesMatch # Used to cause circular import issues
10
11
  from yaralyzer.encoding_detection.character_encodings import (ENCODINGS_TO_ATTEMPT, SINGLE_BYTE_ENCODINGS,
11
12
  UTF_8, encoding_width, is_wide_utf)
12
13
  from yaralyzer.helpers.bytes_helper import clean_byte_string, truncate_for_encoding
13
- from yaralyzer.helpers.rich_text_helper import prefix_with_plain_text_obj, unprintable_byte_to_text
14
+ from yaralyzer.helpers.rich_text_helper import prefix_with_style, unprintable_byte_to_text
14
15
  from yaralyzer.output.rich_console import ALERT_STYLE, BYTES_BRIGHTER, BYTES_BRIGHTEST, BYTES_NO_DIM, GREY_ADDRESS
15
16
  from yaralyzer.util.logging import log
16
17
 
@@ -144,7 +145,7 @@ class DecodingAttempt:
144
145
  else:
145
146
  return self._failed_to_decode_msg_txt(last_exception)
146
147
 
147
- def _to_rich_text(self, _string: str, bytes_offset: int=0) -> Text:
148
+ def _to_rich_text(self, _string: str, bytes_offset: int = 0) -> Text:
148
149
  """Convert a decoded string to highlighted Text representation"""
149
150
  # Adjust where we start the highlighting given the multibyte nature of the encodings
150
151
  log.debug(f"Stepping through {self.encoding} encoded string...")
@@ -181,4 +182,4 @@ class DecodingAttempt:
181
182
  def _failed_to_decode_msg_txt(self, exception: Optional[Exception]) -> Text:
182
183
  """Set failed_to_decode flag and return a Text object with the error message."""
183
184
  self.failed_to_decode = True
184
- return prefix_with_plain_text_obj(f"(decode failed: {exception})", style='red dim italic')
185
+ return prefix_with_style(f"(decode failed: {exception})", style='red dim italic')
@@ -13,7 +13,7 @@ ASCII = 'ascii'
13
13
  UTF_8 = 'utf-8'
14
14
  UTF_16 = 'utf-16'
15
15
  UTF_32 = 'utf-32'
16
- ISO_8859_1 = 'iso-8859-1'
16
+ ISO_8859_1 = 'iso-8859-1'
17
17
  WINDOWS_1252 = 'windows-1252'
18
18
 
19
19
 
@@ -32,39 +32,39 @@ BOMS = {
32
32
  # ASCII characters that either print nothing, put the cursor in a weird place, or (worst of all) actively
33
33
  # delete stuff you already printed
34
34
  UNPRINTABLE_ASCII = {
35
- 0: 'NUL',
36
- 1: 'SOH', # 'StartHeading',
37
- 2: 'STX', # 'StartText',
35
+ 0: 'NUL', # 'Null',
36
+ 1: 'SOH', # 'StartHeading',
37
+ 2: 'STX', # 'StartText',
38
38
  3: 'ETX',
39
- 4: 'EOT', # End of transmission
40
- 5: 'ENQ', # 'Enquiry',
41
- 6: 'ACK', # 'Acknowledgement',
42
- 7: 'BEL', # 'Bell',
43
- 8: 'BS', # 'BackSpace',
44
- #9: 'HT' # 'HorizontalTab',
45
- #10: 'LF', # 'LineFeed',
46
- 11: 'VT', # 'VerticalTab',
47
- 12: 'FF', # 'FormFeed', AKA 'NewPage'
48
- 13: 'CR', # 'CarriageReturn',
49
- 14: 'SO', # 'ShiftOut',
50
- 15: 'SI', # 'ShiftIn',
51
- 16: 'DLE', # 'DataLineEscape',
52
- 17: 'DC1', # DeviceControl1',
53
- 18: 'DC2', # 'DeviceControl2',
54
- 19: 'DC3', # 'DeviceControl3',
55
- 20: 'DC4', # 'DeviceControl4',
39
+ 4: 'EOT', # End of transmission
40
+ 5: 'ENQ', # 'Enquiry',
41
+ 6: 'ACK', # 'Acknowledgement',
42
+ 7: 'BEL', # 'Bell',
43
+ 8: 'BS', # 'BackSpace',
44
+ # 9: 'HT' # 'HorizontalTab',
45
+ # 10: 'LF', # 'LineFeed',
46
+ 11: 'VT', # 'VerticalTab',
47
+ 12: 'FF', # 'FormFeed', AKA 'NewPage'
48
+ 13: 'CR', # 'CarriageReturn',
49
+ 14: 'SO', # 'ShiftOut',
50
+ 15: 'SI', # 'ShiftIn',
51
+ 16: 'DLE', # 'DataLineEscape',
52
+ 17: 'DC1', # DeviceControl1',
53
+ 18: 'DC2', # 'DeviceControl2',
54
+ 19: 'DC3', # 'DeviceControl3',
55
+ 20: 'DC4', # 'DeviceControl4',
56
56
  21: 'NAK', # NegativeAcknowledgement',
57
- 22: 'SYN', # 'SynchronousIdle',
58
- 23: 'ETB', # 'EndTransmitBlock',
59
- 24: 'CAN', # 'Cancel',
60
- 25: 'EM', # 'EndMedium',
61
- 26: 'SUB', # 'Substitute',
62
- 27: 'ESC', # 'Escape',
63
- 28: 'FS', # 'FileSeparator',
64
- 29: 'GS', #'GroupSeparator',
65
- 30: 'RS', #'RecordSeparator',
66
- 31: 'US', # 'UnitSeparator',
67
- 127: 'DEL', # Delete
57
+ 22: 'SYN', # 'SynchronousIdle',
58
+ 23: 'ETB', # 'EndTransmitBlock',
59
+ 24: 'CAN', # 'Cancel',
60
+ 25: 'EM', # 'EndMedium',
61
+ 26: 'SUB', # 'Substitute',
62
+ 27: 'ESC', # 'Escape',
63
+ 28: 'FS', # 'FileSeparator',
64
+ 29: 'GS', # 'GroupSeparator',
65
+ 30: 'RS', # 'RecordSeparator',
66
+ 31: 'US', # 'UnitSeparator',
67
+ 127: 'DEL', # Delete
68
68
  }
69
69
 
70
70
 
@@ -116,12 +116,12 @@ UNPRINTABLE_UTF_8.update({
116
116
  UNPRINTABLE_WIN_1252 = UNPRINTABLE_ASCII.copy()
117
117
 
118
118
  UNPRINTABLE_WIN_1252.update({
119
- 129: 'HOP', # High Octet Preset
120
- 141: 'RLF', # Reverse Line Feed
121
- 143: 'SS3', # Single shift 3
122
- 144: 'DCS', # Device Control String
123
- 147: 'STS', # Set transmit state
124
- 160: 'NBSP',
119
+ 129: 'HOP', # High Octet Preset
120
+ 141: 'RLF', # Reverse Line Feed
121
+ 143: 'SS3', # Single shift 3
122
+ 144: 'DCS', # Device Control String
123
+ 147: 'STS', # Set transmit state
124
+ 160: 'NBSP', # Non-breaking space
125
125
  })
126
126
 
127
127
 
@@ -146,7 +146,6 @@ ENCODINGS_TO_ATTEMPT = {
146
146
  UTF_32: None, # UTF-16 and 32 are handled differently
147
147
  ISO_8859_1: UNPRINTABLE_ISO_8859_1,
148
148
  WINDOWS_1252: UNPRINTABLE_WIN_1252,
149
- #'utf-7':
150
149
  }
151
150
 
152
151
  SINGLE_BYTE_ENCODINGS = [
@@ -7,7 +7,7 @@ from rich.text import Text
7
7
 
8
8
  from yaralyzer.encoding_detection.character_encodings import ENCODING
9
9
  from yaralyzer.helpers.rich_text_helper import (DIM_COUNTRY_THRESHOLD, meter_style,
10
- prefix_with_plain_text_obj)
10
+ prefix_with_style)
11
11
 
12
12
  CONFIDENCE = 'confidence'
13
13
  LANGUAGE = 'language'
@@ -20,7 +20,7 @@ class EncodingAssessment:
20
20
 
21
21
  # Shift confidence from 0-1.0 scale to 0-100.0 scale
22
22
  self.confidence = 100.0 * (self._get_dict_empty_value_as_None(CONFIDENCE) or 0.0)
23
- self.confidence_text = prefix_with_plain_text_obj(f"{round(self.confidence, 1)}%", style=meter_style(self.confidence))
23
+ self.confidence_text = prefix_with_style(f"{round(self.confidence, 1)}%", style=meter_style(self.confidence))
24
24
 
25
25
  # Add detected language info and label if any language was detected
26
26
  self.language = self._get_dict_empty_value_as_None(LANGUAGE)
@@ -9,7 +9,6 @@ import chardet
9
9
  from rich import box
10
10
  from rich.padding import Padding
11
11
  from rich.table import Table
12
- from rich.text import Text
13
12
 
14
13
  from yaralyzer.config import YaralyzerConfig
15
14
  from yaralyzer.encoding_detection.encoding_assessment import ENCODING, EncodingAssessment
@@ -83,7 +82,7 @@ class EncodingDetector:
83
82
  self.unique_assessments.append(result)
84
83
  already_seen_encodings[result.encoding] = result
85
84
  else:
86
- log.debug(f"Skipping chardet result {result}: we already saw {already_seen_encodings[result.encoding]})")
85
+ log.debug(f"Skipping chardet result {result} (already saw {already_seen_encodings[result.encoding]})")
87
86
 
88
87
  self.unique_assessments.sort(key=attrgetter('confidence'), reverse=True)
89
88
 
@@ -105,8 +104,8 @@ def _empty_chardet_results_table():
105
104
  style='dim',
106
105
  box=box.SIMPLE,
107
106
  show_edge=False,
108
- collapse_padding=True)
107
+ collapse_padding=True
108
+ )
109
109
 
110
110
  table.columns[0].justify = 'right'
111
- table.columns # TODO: ???
112
111
  return table
@@ -144,9 +144,9 @@ def truncate_for_encoding(_bytes: bytes, encoding: str) -> bytes:
144
144
  def _find_str_rep_of_bytes(surrounding_bytes_str: str, highlighted_bytes_str: str, highlighted_bytes: BytesMatch):
145
145
  """
146
146
  Find the position of bytes_str in surrounding_byte_str. Both args are raw text dumps of binary data.
147
- Because strings are longer than bytes (stuff like '\xcc' are 4 chars when printed are one byte and the ANSI unprintables
148
- include stuff like 'NegativeAcknowledgement' which is over 20 chars) they represent so we have to re-find the location to highlight the bytes
149
- correctly.
147
+ Because strings are longer than bytes (stuff like '\xcc' are 4 chars when printed are one byte and
148
+ the ANSI unprintables include stuff like 'NegativeAcknowledgement' which is over 20 chars) they represent
149
+ so we have to re-find the location to highlight the bytes correctly.
150
150
  """
151
151
  # Start a few chars in to avoid errors: sometimes we're searching for 1 or 2 bytes and there's a false positive
152
152
  # in the extra bytes. Tthis isn't perfect - it's starting us at the first index into the *bytes* that's safe to
@@ -155,7 +155,7 @@ def _find_str_rep_of_bytes(surrounding_bytes_str: str, highlighted_bytes_str: st
155
155
 
156
156
  # TODO: Somehow \' and ' don't always come out the same :(
157
157
  if highlight_idx == -1:
158
- log.info(f"Failed to find highlighted_bytes in first pass so deleting single quotes and retrying. " + \
158
+ log.info(f"Failed to find highlighted_bytes in first pass so deleting single quotes and retrying. " +
159
159
  "Highlighting may be off by a few chars,")
160
160
 
161
161
  surrounding_bytes_str = surrounding_bytes_str.replace("\\'", "'")
@@ -1,7 +1,6 @@
1
1
  """
2
2
  Help with dicts.
3
3
  """
4
- from numbers import Number
5
4
 
6
5
 
7
6
  def get_dict_key_by_value(_dict: dict, value):
@@ -2,6 +2,7 @@
2
2
  Help with lists.
3
3
  """
4
4
 
5
+
5
6
  def flatten(a):
6
7
  """From https://www.geeksforgeeks.org/python/python-flatten-list-to-individual-elements/"""
7
8
  return_value = []
@@ -40,7 +40,7 @@ def na_txt(style: Union[str, Style] = 'white'):
40
40
  return Text('N/A', style=style)
41
41
 
42
42
 
43
- def prefix_with_plain_text_obj(_str: str, style: str, root_style=None) -> Text:
43
+ def prefix_with_style(_str: str, style: str, root_style=None) -> Text:
44
44
  """Sometimes you need a Text() object to start plain lest the underline or whatever last forever"""
45
45
  return Text('', style=root_style or 'white') + Text(_str, style)
46
46
 
@@ -61,7 +61,7 @@ def meter_style(meter_pct):
61
61
  return style
62
62
 
63
63
 
64
- def unprintable_byte_to_text(code: str, style='') -> Text:
64
+ def unprintable_byte_to_text(code: str, style: str = '') -> Text:
65
65
  """Used with ASCII escape codes and the like, gives colored results like '[NBSP]'."""
66
66
  style = BYTES_HIGHLIGHT if style == BYTES_BRIGHTEST else style
67
67
  txt = Text('[', style=style)
@@ -70,7 +70,7 @@ def unprintable_byte_to_text(code: str, style='') -> Text:
70
70
  return txt
71
71
 
72
72
 
73
- def dim_if(txt: Union[str, Text], is_dim: bool, style: Union[str, None]=None):
73
+ def dim_if(txt: Union[str, Text], is_dim: bool, style: Union[str, None] = None):
74
74
  """Apply 'dim' style if 'is_dim'. 'style' overrides for Text and applies for strings."""
75
75
  txt = txt.copy() if isinstance(txt, Text) else Text(txt, style=style or '')
76
76
 
@@ -95,24 +95,24 @@ def show_color_theme(styles: dict) -> None:
95
95
  console.print(Panel('The Yaralyzer Color Theme', style='reverse'))
96
96
 
97
97
  colors = [
98
- prefix_with_plain_text_obj(name[:MAX_THEME_COL_SIZE], style=str(style)).append(' ')
98
+ prefix_with_style(name[:MAX_THEME_COL_SIZE], style=str(style)).append(' ')
99
99
  for name, style in styles.items()
100
100
  if name not in ['reset', 'repr_url']
101
101
  ]
102
102
 
103
- console.print(Columns(colors, column_first=True, padding=(0,5), equal=True))
103
+ console.print(Columns(colors, column_first=True, padding=(0, 5), equal=True))
104
104
 
105
105
 
106
106
  def size_text(num_bytes: int) -> Text:
107
107
  """Convert a number of bytes into (e.g.) 54,213 bytes (52 KB)"""
108
- kb_txt = prefix_with_plain_text_obj("{:,.1f}".format(num_bytes / 1024), style='bright_cyan', root_style='white')
108
+ kb_txt = prefix_with_style("{:,.1f}".format(num_bytes / 1024), style='bright_cyan', root_style='white')
109
109
  kb_txt.append(' kb ')
110
110
  bytes_txt = Text('(', 'white') + size_in_bytes_text(num_bytes) + Text(')')
111
111
  return kb_txt + bytes_txt
112
112
 
113
113
 
114
114
  def size_in_bytes_text(num_bytes: int) -> Text:
115
- return Text(f"{num_bytes:,d}", 'number').append(' bytes', style='white')
115
+ return Text(f"{num_bytes:,d}", 'number').append(' bytes', style='white')
116
116
 
117
117
 
118
118
  def newline_join(texts: List[Text]) -> Text:
@@ -83,7 +83,7 @@ def invoke_rich_export(export_method, output_file_basepath) -> str:
83
83
  kwargs.update({'clear': False})
84
84
 
85
85
  if 'svg' in method_name:
86
- kwargs.update({'title': path.basename(output_file_path) })
86
+ kwargs.update({'title': path.basename(output_file_path)})
87
87
 
88
88
  # Invoke it
89
89
  log_and_print(f"Invoking Rich.console.{method_name}('{output_file_path}') with kwargs: '{kwargs}'...")
@@ -14,10 +14,10 @@ BytesInfo = namedtuple('BytesInfo', ['size', 'md5', 'sha1', 'sha256'])
14
14
 
15
15
 
16
16
  def bytes_hashes_table(
17
- bytes_or_bytes_info: Union[bytes, BytesInfo],
18
- title: Optional[str] = None,
19
- title_justify: str = LEFT
20
- ) -> Table:
17
+ bytes_or_bytes_info: Union[bytes, BytesInfo],
18
+ title: Optional[str] = None,
19
+ title_justify: str = LEFT
20
+ ) -> Table:
21
21
  """Build a table to show the MD5, SHA1, SHA256, etc."""
22
22
  if isinstance(bytes_or_bytes_info, bytes):
23
23
  bytes_info = compute_file_hashes(bytes_or_bytes_info)
@@ -123,5 +123,5 @@ def print_fatal_error_and_exit(error_message: str) -> None:
123
123
  exit()
124
124
 
125
125
 
126
- def print_header_panel(headline: str, style: str, expand: bool = True, padding: tuple = (0,2)) -> None:
126
+ def print_header_panel(headline: str, style: str, expand: bool = True, padding: tuple = (0, 2)) -> None:
127
127
  console.print(Panel(headline, box=box.DOUBLE_EDGE, style=style, expand=expand, padding=padding))
@@ -78,8 +78,8 @@ source.add_argument('--regex-modifier', '-mod',
78
78
  # Fine tuning
79
79
  tuning = parser.add_argument_group(
80
80
  'FINE TUNING',
81
- "Tune various aspects of the analyses and visualizations to your needs. As an example setting " + \
82
- "a low --max-decode-length (or suppressing brute force binary decode attempts altogether) can " + \
81
+ "Tune various aspects of the analyses and visualizations to your needs. As an example setting " +
82
+ "a low --max-decode-length (or suppressing brute force binary decode attempts altogether) can " +
83
83
  "dramatically improve run times and only occasionally leads to a fatal lack of insight.")
84
84
 
85
85
  tuning.add_argument('--maximize-width', action='store_true',
@@ -119,14 +119,14 @@ tuning.add_argument('--min-chardet-bytes',
119
119
  type=int)
120
120
 
121
121
  tuning.add_argument('--min-chardet-table-confidence',
122
- help="minimum chardet confidence to display the encoding name/score in the character " + \
122
+ help="minimum chardet confidence to display the encoding name/score in the character " +
123
123
  "decection scores table",
124
124
  default=YaralyzerConfig.DEFAULT_MIN_CHARDET_TABLE_CONFIDENCE,
125
125
  metavar='PCT_CONFIDENCE',
126
126
  type=int)
127
127
 
128
128
  tuning.add_argument('--force-display-threshold',
129
- help="encodings with chardet confidence below this number will neither be displayed nor " + \
129
+ help="encodings with chardet confidence below this number will neither be displayed nor " +
130
130
  "decoded in the decodings table",
131
131
  default=EncodingDetector.force_display_threshold,
132
132
  metavar='PCT_CONFIDENCE',
@@ -134,9 +134,9 @@ tuning.add_argument('--force-display-threshold',
134
134
  choices=CONFIDENCE_SCORE_RANGE)
135
135
 
136
136
  tuning.add_argument('--force-decode-threshold',
137
- help="extremely high (AKA 'above this number') confidence scores from chardet.detect() " + \
138
- "as to the likelihood some bytes were written with a particular encoding will cause " + \
139
- "the yaralyzer to attempt decoding those bytes in that encoding even if it is not a " + \
137
+ help="extremely high (AKA 'above this number') confidence scores from chardet.detect() " +
138
+ "as to the likelihood some bytes were written with a particular encoding will cause " +
139
+ "the yaralyzer to attempt decoding those bytes in that encoding even if it is not a " +
140
140
  "configured encoding",
141
141
  default=EncodingDetector.force_decode_threshold,
142
142
  metavar='PCT_CONFIDENCE',
@@ -159,8 +159,8 @@ tuning.add_argument('--yara-stack-size',
159
159
  # Export options
160
160
  export = parser.add_argument_group(
161
161
  'FILE EXPORT',
162
- "Multiselect. Choosing nothing is choosing nothing. Sends what you see on the screen to various file " + \
163
- "formats in parallel. Writes files to the current directory if --output-dir is not provided. " + \
162
+ "Multiselect. Choosing nothing is choosing nothing. Sends what you see on the screen to various file " +
163
+ "formats in parallel. Writes files to the current directory if --output-dir is not provided. " +
164
164
  "Filenames are expansions of the scanned filename though you can use --file-prefix to make your " +
165
165
  "filenames more unique and beautiful to their beholder.")
166
166
 
@@ -282,7 +282,7 @@ def parse_arguments(args: Optional[Namespace] = None):
282
282
 
283
283
  def get_export_basepath(args: Namespace, yaralyzer: Yaralyzer):
284
284
  file_prefix = (args.file_prefix + '_') if args.file_prefix else ''
285
- args.output_basename = f"{file_prefix}{yaralyzer._filename_string()}"
285
+ args.output_basename = f"{file_prefix}{yaralyzer._filename_string()}" # noqa: E221
286
286
  args.output_basename += f"__maxdecode{YaralyzerConfig.args.max_decode_length}"
287
287
  args.output_basename += ('_' + args.file_suffix) if args.file_suffix else ''
288
288
  return path.join(args.output_dir, args.output_basename + f"__at_{args.invoked_at_str}")
@@ -26,7 +26,7 @@ Python log levels for reference:
26
26
  """
27
27
  import logging
28
28
  import sys
29
- from os import environ, path
29
+ from os import path
30
30
  from typing import Union
31
31
 
32
32
  from rich.logging import RichHandler
@@ -97,7 +97,7 @@ def _rich_yara_match(element: Any, depth: int = 0) -> Text:
97
97
  list_txt = Text('[', style='white')
98
98
 
99
99
  if total_length > console_width() or len(element) > 3:
100
- join_txt = Text(f"\n{indent}" )
100
+ join_txt = Text(f"\n{indent}")
101
101
  list_txt.append(join_txt).append(Text(f",{join_txt}").join(elements_txt))
102
102
  list_txt += Text(f'\n{end_indent}]', style='white')
103
103
  else:
@@ -4,12 +4,11 @@ Builds bare bones YARA rules to match strings and regex patterns. Example rule s
4
4
  rule Just_A_Piano_Man {
5
5
  meta:
6
6
  author = "Tim"
7
- strings:
8
- $hilton_producer = /Scott.*Storch/
9
- condition:
10
- $hilton_producer
7
+ strings:
8
+ $hilton_producer = /Scott.*Storch/
9
+ condition:
10
+ $hilton_producer
11
11
  }
12
-
13
12
  """
14
13
  import re
15
14
  from typing import Optional
@@ -60,12 +59,12 @@ rule {rule_name} {{
60
59
 
61
60
 
62
61
  def yara_rule_string(
63
- pattern: str,
64
- pattern_type: str = REGEX,
65
- rule_name: str = YARALYZE,
66
- pattern_label: Optional[str] = PATTERN,
67
- modifier: Optional[str] = None
68
- ) -> str:
62
+ pattern: str,
63
+ pattern_type: str = REGEX,
64
+ rule_name: str = YARALYZE,
65
+ pattern_label: Optional[str] = PATTERN,
66
+ modifier: Optional[str] = None
67
+ ) -> str:
69
68
  """Build a YARA rule string for a given pattern"""
70
69
  if not (modifier is None or modifier in YARA_REGEX_MODIFIERS):
71
70
  raise TypeError(f"Modifier '{modifier}' is not one of {YARA_REGEX_MODIFIERS}")
@@ -89,12 +88,12 @@ def yara_rule_string(
89
88
 
90
89
 
91
90
  def build_yara_rule(
92
- pattern: str,
93
- pattern_type: str = REGEX,
94
- rule_name: str = YARALYZE,
95
- pattern_label: Optional[str] = PATTERN,
96
- modifier: Optional[str] = None
97
- ) -> yara.Rule:
91
+ pattern: str,
92
+ pattern_type: str = REGEX,
93
+ rule_name: str = YARALYZE,
94
+ pattern_label: Optional[str] = PATTERN,
95
+ modifier: Optional[str] = None
96
+ ) -> yara.Rule:
98
97
  """Build a compiled YARA rule"""
99
98
  rule_string = yara_rule_string(pattern, pattern_type, rule_name, pattern_label, modifier)
100
99
  return yara.compile(source=rule_string)
@@ -9,7 +9,6 @@ Alternate constructors are provided depending on whether:
9
9
  The real action happens in the __rich__console__() dunder method.
10
10
  """
11
11
  from os import path
12
- from sys import exit
13
12
  from typing import Iterator, List, Optional, Tuple, Union
14
13
 
15
14
  import yara
@@ -36,13 +35,13 @@ YARA_FILE_DOES_NOT_EXIST_ERROR_MSG = "is not a valid yara rules file (it doesn't
36
35
  # TODO: might be worth introducing a Scannable namedtuple or similar
37
36
  class Yaralyzer:
38
37
  def __init__(
39
- self,
40
- rules: Union[str, yara.Rules],
41
- rules_label: str,
42
- scannable: Union[bytes, str],
43
- scannable_label: Optional[str] = None,
44
- highlight_style: str = YaralyzerConfig.HIGHLIGHT_STYLE
45
- ) -> None:
38
+ self,
39
+ rules: Union[str, yara.Rules],
40
+ rules_label: str,
41
+ scannable: Union[bytes, str],
42
+ scannable_label: Optional[str] = None,
43
+ highlight_style: str = YaralyzerConfig.HIGHLIGHT_STYLE
44
+ ) -> None:
46
45
  """
47
46
  If rules is a string it will be compiled by yara
48
47
  If scannable is bytes then scannable_label must be provided.
@@ -83,11 +82,11 @@ class Yaralyzer:
83
82
 
84
83
  @classmethod
85
84
  def for_rules_files(
86
- cls,
87
- yara_rules_files: List[str],
88
- scannable: Union[bytes, str],
89
- scannable_label: Optional[str] = None
90
- ) -> 'Yaralyzer':
85
+ cls,
86
+ yara_rules_files: List[str],
87
+ scannable: Union[bytes, str],
88
+ scannable_label: Optional[str] = None
89
+ ) -> 'Yaralyzer':
91
90
  """Alternate constructor loads yara rules from files, labels rules w/filenames"""
92
91
  if not isinstance(yara_rules_files, list):
93
92
  raise TypeError(f"{yara_rules_files} is not a list")
@@ -108,11 +107,11 @@ class Yaralyzer:
108
107
 
109
108
  @classmethod
110
109
  def for_rules_dirs(
111
- cls,
112
- dirs: List[str],
113
- scannable: Union[bytes, str],
114
- scannable_label: Optional[str] = None
115
- ) -> 'Yaralyzer':
110
+ cls,
111
+ dirs: List[str],
112
+ scannable: Union[bytes, str],
113
+ scannable_label: Optional[str] = None
114
+ ) -> 'Yaralyzer':
116
115
  """Alternate constructor that will load all .yara files in yara_rules_dir"""
117
116
  if not (isinstance(dirs, list) and all(path.isdir(dir) for dir in dirs)):
118
117
  raise TypeError(f"'{dirs}' is not a list of valid directories")
@@ -122,15 +121,15 @@ class Yaralyzer:
122
121
 
123
122
  @classmethod
124
123
  def for_patterns(
125
- cls,
126
- patterns: List[str],
127
- patterns_type: str,
128
- scannable: Union[bytes, str],
129
- scannable_label: Optional[str] = None,
130
- rules_label: Optional[str] = None,
131
- pattern_label: Optional[str] = None,
132
- regex_modifier: Optional[str] = None,
133
- ) -> 'Yaralyzer':
124
+ cls,
125
+ patterns: List[str],
126
+ patterns_type: str,
127
+ scannable: Union[bytes, str],
128
+ scannable_label: Optional[str] = None,
129
+ rules_label: Optional[str] = None,
130
+ pattern_label: Optional[str] = None,
131
+ regex_modifier: Optional[str] = None,
132
+ ) -> 'Yaralyzer':
134
133
  """Constructor taking regex pattern strings. Rules label defaults to patterns joined by comma"""
135
134
  rule_strings = []
136
135
 
@@ -186,7 +185,7 @@ class Yaralyzer:
186
185
  # Only show the non matches if there were valid ones, otherwise just show the number
187
186
  if len(self.matches) == 0:
188
187
  non_match_desc = f" did not match any of the {len(self.non_matches)} yara rules"
189
- console.print(dim_if(self.__text__() + Text(non_match_desc, style='grey'), True))
188
+ console.print(dim_if(self.__text__() + Text(non_match_desc, style='grey'), True))
190
189
  return
191
190
 
192
191
  non_match_desc = f" did not match the other {len(self.non_matches)} yara rules"
File without changes
File without changes
File without changes