dissect.cobaltstrike 1.2.2.dev2__tar.gz → 1.2.2.dev3__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.
Files changed (73) hide show
  1. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/.pre-commit-config.yaml +8 -9
  2. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/PKG-INFO +7 -5
  3. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/README.rst +1 -1
  4. dissect_cobaltstrike-1.2.2.dev3/dissect/cobaltstrike/_version.py +24 -0
  5. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/dissect/cobaltstrike/beacon.py +1 -1
  6. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/dissect/cobaltstrike/pe.py +44 -27
  7. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/dissect/cobaltstrike/xordecode.py +1 -1
  8. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/docs/installation.rst +12 -2
  9. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/pyproject.toml +40 -3
  10. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/tests/test_beacon.py +2 -4
  11. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/tests/test_c2profile.py +1 -1
  12. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/tox.ini +11 -1
  13. dissect_cobaltstrike-1.2.2.dev2/dissect/cobaltstrike/_version.py +0 -21
  14. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/.github/workflows/dissect-ci.yml +0 -0
  15. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/.gitignore +0 -0
  16. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/.lfs-assets-id +0 -0
  17. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/.readthedocs.yml +0 -0
  18. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/LICENSE +0 -0
  19. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/codecov.yml +0 -0
  20. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/dissect/cobaltstrike/__init__.py +0 -0
  21. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/dissect/cobaltstrike/artifact.py +0 -0
  22. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/dissect/cobaltstrike/c2.py +0 -0
  23. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/dissect/cobaltstrike/c2profile.lark +0 -0
  24. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/dissect/cobaltstrike/c2profile.py +0 -0
  25. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/dissect/cobaltstrike/c_c2.py +0 -0
  26. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/dissect/cobaltstrike/client.py +0 -0
  27. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/dissect/cobaltstrike/guardrails.py +2 -2
  28. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/dissect/cobaltstrike/pcap.py +0 -0
  29. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/dissect/cobaltstrike/utils.py +0 -0
  30. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/dissect/cobaltstrike/version.py +0 -0
  31. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/docs/Makefile +0 -0
  32. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/docs/_ext/generate_beacon_version_csv.py +0 -0
  33. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/docs/beacon-version-identification.rst +0 -0
  34. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/docs/c2profile-grammar.rst +0 -0
  35. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/docs/cobaltstrike-beacon-versions.csv +0 -0
  36. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/docs/codespell-ignore-words.txt +0 -0
  37. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/docs/conf.py +0 -0
  38. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/docs/examples.rst +0 -0
  39. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/docs/images/beacon-client.png +0 -0
  40. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/docs/images/teamserver-echoclient.png +0 -0
  41. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/docs/images/teamserver_ls.png +0 -0
  42. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/docs/images/vt-cobaltstrike-43-zip.png +0 -0
  43. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/docs/index.rst +0 -0
  44. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/docs/license.rst +0 -0
  45. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/docs/scripts/checksum8-accesslogs.rst +0 -0
  46. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/docs/scripts/dump_beacon_keys.rst +0 -0
  47. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/docs/scripts/example_client.rst +0 -0
  48. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/docs/scripts/index.rst +0 -0
  49. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/docs/structures/c2_def.rst +0 -0
  50. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/docs/structures/cs_def.rst +0 -0
  51. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/docs/structures/guardrails_def.rst +0 -0
  52. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/docs/structures/index.rst +0 -0
  53. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/docs/structures/pe_def.rst +0 -0
  54. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/docs/tools/beacon-artifact.rst +0 -0
  55. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/docs/tools/beacon-client.rst +0 -0
  56. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/docs/tools/beacon-dump.rst +0 -0
  57. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/docs/tools/beacon-pcap.rst +0 -0
  58. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/docs/tools/beacon-xordecode.rst +0 -0
  59. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/docs/tools/c2profile-dump.rst +0 -0
  60. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/docs/tutorials/decrypt_cobaltstrike_pcaps.rst +0 -0
  61. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/docs/tutorials/index.rst +0 -0
  62. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/docs/tutorials/minimal_beacon_client.rst +0 -0
  63. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/scripts/checksum8-accesslogs.py +0 -0
  64. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/scripts/dump_beacon_keys.py +0 -0
  65. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/scripts/example_client.py +0 -0
  66. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/tests/conftest.py +0 -0
  67. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/tests/test_c2.py +0 -0
  68. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/tests/test_client.py +0 -0
  69. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/tests/test_guardrails.py +0 -0
  70. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/tests/test_pe.py +0 -0
  71. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/tests/test_utils.py +0 -0
  72. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/tests/test_version.py +0 -0
  73. {dissect_cobaltstrike-1.2.2.dev2 → dissect_cobaltstrike-1.2.2.dev3}/tests/test_xordecode.py +0 -0
@@ -1,16 +1,15 @@
1
1
  repos:
2
2
  - repo: https://github.com/charliermarsh/ruff-pre-commit
3
- rev: v0.6.9
3
+ rev: v0.15.6
4
4
  hooks:
5
- - id: ruff
6
- - repo: https://github.com/psf/black
7
- rev: 24.10.0
8
- hooks:
9
- - id: black
10
- args: [--check, --diff]
11
- language_version: python3
5
+ - id: ruff # used by `fix`
6
+ alias: ruff-fix
7
+ args: [--fix]
8
+ - id: ruff # used by `lint`
9
+ alias: ruff-lint
10
+ - id: ruff-format # replaces black
12
11
  - repo: https://github.com/codespell-project/codespell
13
- rev: v2.3.0
12
+ rev: v2.4.2
14
13
  hooks:
15
14
  - id: codespell
16
15
  args: [--ignore-words=docs/codespell-ignore-words.txt]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dissect.cobaltstrike
3
- Version: 1.2.2.dev2
3
+ Version: 1.2.2.dev3
4
4
  Summary: a Python library for dissecting Cobalt Strike related data
5
5
  Project-URL: Homepage, https://github.com/fox-it/dissect.cobaltstrike
6
6
  Project-URL: Documentation, https://dissect-cobaltstrike.readthedocs.io/
@@ -19,17 +19,19 @@ Classifier: Operating System :: MacOS :: MacOS X
19
19
  Classifier: Operating System :: Microsoft :: Windows
20
20
  Classifier: Operating System :: POSIX
21
21
  Classifier: Programming Language :: Python :: 3 :: Only
22
- Classifier: Programming Language :: Python :: 3.9
23
22
  Classifier: Programming Language :: Python :: 3.10
24
23
  Classifier: Programming Language :: Python :: 3.11
25
24
  Classifier: Programming Language :: Python :: 3.12
26
25
  Classifier: Programming Language :: Python :: 3.13
26
+ Classifier: Programming Language :: Python :: 3.14
27
27
  Classifier: Topic :: Scientific/Engineering :: Information Analysis
28
28
  Classifier: Topic :: Security
29
29
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
30
30
  Classifier: Topic :: Utilities
31
- Requires-Python: >=3.9
32
- Requires-Dist: dissect-cstruct>=4.6.dev
31
+ Requires-Python: >=3.10
32
+ Requires-Dist: dissect-cstruct>=4.7
33
+ Requires-Dist: dissect-executable>=1.11
34
+ Requires-Dist: dissect-util>=3.24
33
35
  Requires-Dist: lark
34
36
  Provides-Extra: c2
35
37
  Requires-Dist: flow-record; extra == 'c2'
@@ -99,7 +101,7 @@ If you want to install the latest pre-release version, use the ``--pre`` flag::
99
101
 
100
102
  $ pip install --pre dissect.cobaltstrike
101
103
 
102
- **dissect.cobaltstrike** requires Python 3.9 or later.
104
+ **dissect.cobaltstrike** requires Python 3.10 or later.
103
105
 
104
106
  Documentation
105
107
  -------------
@@ -27,7 +27,7 @@ If you want to install the latest pre-release version, use the ``--pre`` flag::
27
27
 
28
28
  $ pip install --pre dissect.cobaltstrike
29
29
 
30
- **dissect.cobaltstrike** requires Python 3.9 or later.
30
+ **dissect.cobaltstrike** requires Python 3.10 or later.
31
31
 
32
32
  Documentation
33
33
  -------------
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '1.2.2.dev3'
22
+ __version_tuple__ = version_tuple = (1, 2, 2, 'dev3')
23
+
24
+ __commit_id__ = commit_id = None
@@ -369,7 +369,7 @@ def iter_beacon_config_blocks(
369
369
  # Try XorEncoded files first as they are more common
370
370
  if not found and xordecode:
371
371
  try:
372
- fxor = cast(BinaryIO, XorEncodedFile.from_file(fobj))
372
+ fxor = cast("BinaryIO", XorEncodedFile.from_file(fobj))
373
373
  for xorkey in xor_keys:
374
374
  for config_block in find_beacon_config_bytes(fxor, xorkey):
375
375
  found = True
@@ -8,6 +8,10 @@ import io
8
8
  import logging
9
9
  from typing import BinaryIO, Optional, Tuple
10
10
 
11
+ from dissect.executable import PE
12
+ from dissect.executable.pe.c_pe import c_pe
13
+ from dissect.util.stream import OverlayStream, RangeStream
14
+
11
15
  from dissect import cstruct
12
16
 
13
17
  logger = logging.getLogger(__name__)
@@ -174,6 +178,39 @@ pestruct.load(PE_DEF)
174
178
  DOSHEADER_X64 = bytes.fromhex("554889e54881")
175
179
  DOSHEADER_X86 = bytes.fromhex("e8000000005b")
176
180
 
181
+ NT_SIGNATURE = c_pe.IMAGE_NT_SIGNATURE.to_bytes(4, "little")
182
+ OS2_SIGNATURE = c_pe.IMAGE_OS2_SIGNATURE.to_bytes(4, "little")
183
+ IMAGE_DOS_SIGNATURE = c_pe.IMAGE_DOS_SIGNATURE.to_bytes(2, "little")
184
+
185
+
186
+ class LenientPE(PE):
187
+ """Wrapper around :class:`dissect.executable.PE` that is lenient towards invalid PE signatures.
188
+
189
+ This is accomplished by using an OverlayStream to patch the PE signature when it's invalid.
190
+
191
+ It currently overlays the following fixes:
192
+
193
+ - Invalid PE signature (not "PE\0\0")
194
+ - Invalid MZ signature (not "MZ")
195
+ """
196
+
197
+ def __init__(self, fh: BinaryIO) -> None:
198
+ fh.seek(0)
199
+ mz_header = c_pe.IMAGE_DOS_HEADER(fh)
200
+ overlay = OverlayStream(fh, size=fh.seek(0, io.SEEK_END))
201
+ if mz_header.e_magic != c_pe.IMAGE_DOS_SIGNATURE:
202
+ logger.debug("Patching invalid MZ signature: %r -> %r", mz_header.e_magic, IMAGE_DOS_SIGNATURE)
203
+ overlay.add(0, IMAGE_DOS_SIGNATURE)
204
+ fh = overlay
205
+
206
+ fh.seek(mz_header.e_lfanew, io.SEEK_SET)
207
+ signature = fh.read(4)
208
+ if signature not in (NT_SIGNATURE, OS2_SIGNATURE):
209
+ logger.debug("Patching invalid PE signature: %r -> %r", signature, NT_SIGNATURE)
210
+ overlay.add(mz_header.e_lfanew, NT_SIGNATURE)
211
+ fh = overlay
212
+ super().__init__(fh)
213
+
177
214
 
178
215
  def find_mz_offset(fh: BinaryIO, start_offset: int = 0, maxrange: int = 1024) -> Optional[int]:
179
216
  """Find and return the start offset of a valid IMAGE_DOS_HEADER or ``None`` if it cannot be found.
@@ -226,35 +263,15 @@ def find_compile_stamps(
226
263
  Tuple with ``(IMAGE_FILE_HEADER.TimeDateStamp, IMAGE_EXPORT_DIRECTORY.TimeDateStamp)``.
227
264
  Either tuple values can be ``None`` if it's not found.
228
265
  """
229
- mz_offset = find_mz_offset(fh, start_offset=start_offset, maxrange=maxrange)
230
- if mz_offset is None:
231
- return (None, None)
232
-
233
266
  compile_stamp = None
234
267
  export_stamp = None
235
- fh.seek(mz_offset)
236
- mz = pestruct.IMAGE_DOS_HEADER(fh)
237
- fh.seek(mz.e_lfanew + mz_offset)
238
- signature = pestruct.uint32(fh).to_bytes(4, "little")
239
- logger.debug("PE signature: %r", signature)
240
- image = pestruct.IMAGE_FILE_HEADER(fh)
241
- compile_stamp = image.TimeDateStamp
242
- if image.Machine == pestruct.IMAGE_FILE_MACHINE_AMD64:
243
- optional_header = pestruct.IMAGE_OPTIONAL_HEADER64(fh)
244
- else:
245
- optional_header = pestruct.IMAGE_OPTIONAL_HEADER(fh)
246
- export_dd = optional_header.DataDirectory[pestruct.IMAGE_DIRECTORY_ENTRY_EXPORT]
247
- sections = [pestruct.IMAGE_SECTION_HEADER(fh) for _ in range(image.NumberOfSections)]
248
- ds = None
249
- for section in sections:
250
- if section.VirtualAddress <= export_dd.VirtualAddress < (section.VirtualAddress + section.VirtualSize):
251
- ds = section
252
- break
253
- if ds is not None:
254
- offset = export_dd.VirtualAddress - ds.VirtualAddress + ds.PointerToRawData + mz_offset
255
- fh.seek(offset)
256
- export_dir = pestruct.IMAGE_EXPORT_DIRECTORY(fh)
257
- export_stamp = export_dir.TimeDateStamp
268
+
269
+ mz_offset = find_mz_offset(fh, start_offset=start_offset, maxrange=maxrange)
270
+ if mz_offset is not None:
271
+ pe = LenientPE(RangeStream(fh, offset=mz_offset, size=None))
272
+ compile_stamp = int(pe.timestamp.timestamp())
273
+ export_stamp = int(pe.exports.timestamp.timestamp()) if pe.exports else None
274
+
258
275
  return (compile_stamp, export_stamp)
259
276
 
260
277
 
@@ -118,7 +118,7 @@ class XorEncodedFile(io.RawIOBase):
118
118
  logger.debug(f"Found common nonce offset: {offset} ({count})")
119
119
  found_nonce_offset = offset
120
120
  xf = cls(fh, nonce_offset=found_nonce_offset)
121
- if pe.find_mz_offset(cast(BinaryIO, xf)) is not None:
121
+ if pe.find_mz_offset(cast("BinaryIO", xf)) is not None:
122
122
  xf.seek(0)
123
123
  return xf
124
124
  raise ValueError(f"MZ header not found for: {fh}")
@@ -7,12 +7,16 @@ The easiest way to install ``dissect.cobaltstrike`` is to use **pip**:
7
7
 
8
8
  $ pip install dissect.cobaltstrike
9
9
 
10
- Python 3.9 or higher is required and it has the following dependencies:
10
+ Python 3.10 or higher is required and it has the following dependencies:
11
11
 
12
12
  * dissect.cstruct_ - for structure parsing
13
+ * dissect.executable_ - for PE parsing
14
+ * dissect.util_ - for useful file stream functions
13
15
  * lark_ - for parsing malleable c2 profiles
14
16
 
15
17
  .. _dissect.cstruct: https://github.com/fox-it/dissect.cstruct
18
+ .. _dissect.executable: https://github.com/fox-it/dissect.executable
19
+ .. _dissect.util: https://github.com/fox-it/dissect.util
16
20
  .. _lark: https://github.com/lark-parser/lark
17
21
 
18
22
  The following pip `extras` flavours are provided as well:
@@ -79,12 +83,18 @@ run tests with `checksum8` in the name including verbose and stdout logging:
79
83
  Linting
80
84
  -------
81
85
 
82
- For linting (black and flake8):
86
+ For linting, run:
83
87
 
84
88
  .. code-block:: shell
85
89
 
86
90
  $ tox -e lint
87
91
 
92
+ If there are any linting issues you can try to "fix" them using:
93
+
94
+ .. code-block:: shell
95
+
96
+ $ tox -e fix
97
+
88
98
  Documentation
89
99
  -------------
90
100
 
@@ -6,14 +6,16 @@ build-backend = "hatchling.build"
6
6
  name = "dissect.cobaltstrike"
7
7
  dynamic = ["version"]
8
8
  description = "a Python library for dissecting Cobalt Strike related data"
9
- requires-python = ">=3.9"
9
+ requires-python = ">=3.10"
10
10
  license = {text = "MIT License"}
11
11
  readme = "README.rst"
12
12
  authors = [
13
13
  {name = "Yun Zheng Hu", email = "hu@fox-it.com"},
14
14
  ]
15
15
  dependencies = [
16
- "dissect.cstruct >= 4.6.dev",
16
+ "dissect.cstruct >= 4.7",
17
+ "dissect.executable >= 1.11",
18
+ "dissect.util >= 3.24",
17
19
  "lark",
18
20
  ]
19
21
  keywords = ["dissect", "cobaltstrike", "beacon", "parser", "parsing", "lark", "cstruct"]
@@ -28,11 +30,11 @@ classifiers = [
28
30
  "Operating System :: MacOS :: MacOS X",
29
31
  "Operating System :: Microsoft :: Windows",
30
32
  "Programming Language :: Python :: 3 :: Only",
31
- "Programming Language :: Python :: 3.9",
32
33
  "Programming Language :: Python :: 3.10",
33
34
  "Programming Language :: Python :: 3.11",
34
35
  "Programming Language :: Python :: 3.12",
35
36
  "Programming Language :: Python :: 3.13",
37
+ "Programming Language :: Python :: 3.14",
36
38
  "Topic :: Security",
37
39
  "Topic :: Utilities",
38
40
  "Topic :: Scientific/Engineering :: Information Analysis",
@@ -75,6 +77,41 @@ docs = [
75
77
  "dissect.cobaltstrike[full]",
76
78
  ]
77
79
 
80
+ [dependency-groups]
81
+ c2 = [
82
+ "flow.record",
83
+ "pycryptodome",
84
+ "httpx",
85
+ ]
86
+ pcap = [
87
+ "pyshark",
88
+ {include-group = "c2"},
89
+ ]
90
+ full = [
91
+ {include-group = "c2"},
92
+ {include-group = "pcap"},
93
+ "rich",
94
+ ]
95
+ test = [
96
+ "pytest",
97
+ "pytest-cov",
98
+ "pytest-httpserver",
99
+ {include-group = "full"},
100
+ ]
101
+ lint = [
102
+ "ruff",
103
+ ]
104
+ docs = [
105
+ "sphinx",
106
+ "sphinx_rtd_theme>=2.0",
107
+ "sphinx-autoapi",
108
+ "sphinx-copybutton",
109
+ "sphinx-argparse-cli",
110
+ "ipython",
111
+ "pickleshare",
112
+ {include-group = "full"},
113
+ ]
114
+
78
115
  [project.scripts]
79
116
  beacon-artifact = "dissect.cobaltstrike.artifact:main"
80
117
  beacon-dump = "dissect.cobaltstrike.beacon:main"
@@ -130,9 +130,7 @@ def test_setting_useragent_edgecase():
130
130
  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
131
131
  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
132
132
  00 00 00 00 00 00
133
- """.replace(
134
- "\n", ""
135
- )
133
+ """.replace("\n", "")
136
134
  config = beacon.BeaconConfig(bytes.fromhex(data))
137
135
 
138
136
  ua = "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 10.0; Win64; x64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729)" # noqa: E501
@@ -290,7 +288,7 @@ def test_main(capfdbinary, request, fixture, options, ret, stdout, stderr):
290
288
  def test_beacon_public_key(beacon_x86_file):
291
289
  bconfig = beacon.BeaconConfig.from_file(beacon_x86_file)
292
290
  assert bconfig.public_key == bytes.fromhex(
293
- "30819f300d06092a864886f70d010101050003818d0030818902818100c9bc2d82418688a2e4f8d645ca54dacce652ef725189444fa2def7acfaf0b40000d45933eceb80e7fc1e1e2a540a3e96c2ffc22026369d0ff07da59898f1752593876f69a80b763042abbb92c52f8a85556c5f8e8b052060231684e007a866fc010f69f4c1d79e236b1b90cbc3861bf9b3a366cf5dac02d39519dafc717dece50203010001" # noqa: 501
291
+ "30819f300d06092a864886f70d010101050003818d0030818902818100c9bc2d82418688a2e4f8d645ca54dacce652ef725189444fa2def7acfaf0b40000d45933eceb80e7fc1e1e2a540a3e96c2ffc22026369d0ff07da59898f1752593876f69a80b763042abbb92c52f8a85556c5f8e8b052060231684e007a866fc010f69f4c1d79e236b1b90cbc3861bf9b3a366cf5dac02d39519dafc717dece50203010001" # noqa: E501
294
292
  )
295
293
  assert hashlib.sha256(bconfig.public_key).hexdigest() == bconfig.settings["SETTING_PUBKEY"]
296
294
 
@@ -62,7 +62,7 @@ def test_c2profile_generator():
62
62
 
63
63
 
64
64
  def test_value_to_string():
65
- val = b"\xCA\xFE\xBA\xBE"
65
+ val = b"\xca\xfe\xba\xbe"
66
66
  assert c2profile.value_to_string(val) == '"\\xca\\xfe\\xba\\xbe"'
67
67
  val = b"\x00\x22\x27"
68
68
  assert c2profile.value_to_string(val) == '"\\x00\\"\'"'
@@ -16,7 +16,17 @@ commands =
16
16
  [testenv:lint]
17
17
  deps = pre-commit
18
18
  skip_install = true
19
- commands = pre-commit run --all-files
19
+ commands =
20
+ pre-commit run codespell --all-files
21
+ pre-commit run ruff-lint --all-files
22
+ pre-commit run ruff-format --all-files
23
+
24
+ [testenv:fix]
25
+ deps = pre-commit
26
+ skip_install = true
27
+ commands =
28
+ pre-commit run ruff-fix --all-files
29
+ pre-commit run ruff-format --all-files
20
30
 
21
31
  [testenv:build]
22
32
  skip_install = true
@@ -1,21 +0,0 @@
1
- # file generated by setuptools-scm
2
- # don't change, don't track in version control
3
-
4
- __all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
5
-
6
- TYPE_CHECKING = False
7
- if TYPE_CHECKING:
8
- from typing import Tuple
9
- from typing import Union
10
-
11
- VERSION_TUPLE = Tuple[Union[int, str], ...]
12
- else:
13
- VERSION_TUPLE = object
14
-
15
- version: str
16
- __version__: str
17
- __version_tuple__: VERSION_TUPLE
18
- version_tuple: VERSION_TUPLE
19
-
20
- __version__ = version = '1.2.2.dev2'
21
- __version_tuple__ = version_tuple = (1, 2, 2, 'dev2')
@@ -23,9 +23,10 @@ import logging
23
23
  from dataclasses import dataclass
24
24
  from typing import TYPE_CHECKING, BinaryIO
25
25
 
26
- from dissect.cobaltstrike.utils import grouper, u32be, xor
27
26
  from dissect.cstruct import cstruct
28
27
 
28
+ from dissect.cobaltstrike.utils import grouper, u32be, xor
29
+
29
30
  if TYPE_CHECKING:
30
31
  from collections.abc import Iterator
31
32
 
@@ -172,7 +173,6 @@ def payload_checksum(data: bytes) -> int:
172
173
 
173
174
  def iter_guardrail_configs_with_beacon(fh: BinaryIO) -> Iterator[GuardrailMetadata]:
174
175
  for grconfig in iter_guardrail_configs(fh):
175
-
176
176
  # Unmask the beacon config, static single byte xor key should be 0x2E unless modified beacon
177
177
  # The beacon config is still masked with the environmental key
178
178
  grconfig.beacon_xor_key = b"\x2e" # we currently only support the XOR default key