dissect.cobaltstrike 1.2.1.dev9__tar.gz → 1.2.1.dev10__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.
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/PKG-INFO +2 -2
- dissect_cobaltstrike-1.2.1.dev10/dissect/cobaltstrike/_version.py +21 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/dissect/cobaltstrike/beacon.py +79 -60
- dissect_cobaltstrike-1.2.1.dev10/dissect/cobaltstrike/guardrails.py +195 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/dissect/cobaltstrike/utils.py +8 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/dissect/cobaltstrike/version.py +2 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/tests/conftest.py +2 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/tests/test_beacon.py +42 -0
- dissect_cobaltstrike-1.2.1.dev10/tests/test_guardrails.py +60 -0
- dissect_cobaltstrike-1.2.1.dev9/dissect/cobaltstrike/_version.py +0 -16
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/.github/workflows/dissect-ci.yml +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/.gitignore +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/.lfs-assets-id +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/.pre-commit-config.yaml +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/.readthedocs.yml +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/LICENSE +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/README.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/codecov.yml +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/dissect/cobaltstrike/__init__.py +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/dissect/cobaltstrike/artifact.py +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/dissect/cobaltstrike/c2.py +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/dissect/cobaltstrike/c2profile.lark +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/dissect/cobaltstrike/c2profile.py +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/dissect/cobaltstrike/c_c2.py +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/dissect/cobaltstrike/client.py +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/dissect/cobaltstrike/pcap.py +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/dissect/cobaltstrike/pe.py +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/dissect/cobaltstrike/xordecode.py +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/docs/Makefile +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/docs/c2profile-grammar.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/docs/codespell-ignore-words.txt +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/docs/conf.py +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/docs/examples.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/docs/images/beacon-client.png +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/docs/images/teamserver-echoclient.png +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/docs/images/teamserver_ls.png +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/docs/images/vt-cobaltstrike-43-zip.png +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/docs/index.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/docs/installation.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/docs/license.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/docs/scripts/checksum8-accesslogs.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/docs/scripts/dump_beacon_keys.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/docs/scripts/example_client.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/docs/scripts/index.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/docs/structures/c2_def.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/docs/structures/cs_def.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/docs/structures/index.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/docs/structures/pe_def.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/docs/tools/beacon-artifact.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/docs/tools/beacon-client.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/docs/tools/beacon-dump.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/docs/tools/beacon-pcap.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/docs/tools/beacon-xordecode.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/docs/tools/c2profile-dump.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/docs/tutorials/decrypt_cobaltstrike_pcaps.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/docs/tutorials/index.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/docs/tutorials/minimal_beacon_client.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/pyproject.toml +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/scripts/checksum8-accesslogs.py +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/scripts/dump_beacon_keys.py +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/scripts/example_client.py +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/tests/test_c2.py +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/tests/test_c2profile.py +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/tests/test_client.py +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/tests/test_pe.py +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/tests/test_utils.py +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/tests/test_version.py +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/tests/test_xordecode.py +0 -0
- {dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/tox.ini +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: dissect.cobaltstrike
|
|
3
|
-
Version: 1.2.1.
|
|
3
|
+
Version: 1.2.1.dev10
|
|
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/
|
|
@@ -0,0 +1,21 @@
|
|
|
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.1.dev10'
|
|
21
|
+
__version_tuple__ = version_tuple = (1, 2, 1, 'dev10')
|
{dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/dissect/cobaltstrike/beacon.py
RENAMED
|
@@ -9,7 +9,6 @@ import functools
|
|
|
9
9
|
import hashlib
|
|
10
10
|
import io
|
|
11
11
|
import ipaddress
|
|
12
|
-
import itertools
|
|
13
12
|
import logging
|
|
14
13
|
import sys
|
|
15
14
|
import time
|
|
@@ -36,8 +35,10 @@ from typing import (
|
|
|
36
35
|
|
|
37
36
|
from dissect import cstruct
|
|
38
37
|
from dissect.cobaltstrike import pe
|
|
38
|
+
from dissect.cobaltstrike.guardrails import GuardrailMetadata, iter_guardrail_configs_with_beacon
|
|
39
39
|
from dissect.cobaltstrike.utils import (
|
|
40
40
|
catch_sigpipe,
|
|
41
|
+
grouper,
|
|
41
42
|
iter_find_needle,
|
|
42
43
|
p8,
|
|
43
44
|
u16be,
|
|
@@ -464,13 +465,6 @@ def iter_settings(fobj: Union[bytes, BinaryIO]) -> Iterator["Setting"]:
|
|
|
464
465
|
yield setting
|
|
465
466
|
|
|
466
467
|
|
|
467
|
-
def grouper(iterable, n, fillvalue=None):
|
|
468
|
-
"Collect data into fixed-length chunks or blocks"
|
|
469
|
-
# grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx"
|
|
470
|
-
args = [iter(iterable)] * n
|
|
471
|
-
return itertools.zip_longest(*args, fillvalue=fillvalue)
|
|
472
|
-
|
|
473
|
-
|
|
474
468
|
def parse_recover_binary(program: bytes) -> List[Tuple[str, Union[int, bool]]]:
|
|
475
469
|
"""Parse ``SETTING_C2_RECOVER`` (`.http-get.server.output`) data"""
|
|
476
470
|
rsteps: List[Tuple[str, Union[int, bool]]] = []
|
|
@@ -773,6 +767,8 @@ class BeaconConfig:
|
|
|
773
767
|
""" PE compile timestamp, ``None`` if unknown. """
|
|
774
768
|
self.architecture: Optional[str] = None
|
|
775
769
|
""" PE architecture, ``"x86"`` or ``"x64"`` and ``None`` if unknown. """
|
|
770
|
+
self.guardrails: Optional[GuardrailMetadata] = None
|
|
771
|
+
""" Guardrails metadata, ``None`` if not available. """
|
|
776
772
|
|
|
777
773
|
# Used for caching
|
|
778
774
|
self._settings: Optional[Mapping[str, Any]] = None
|
|
@@ -809,6 +805,22 @@ class BeaconConfig:
|
|
|
809
805
|
bconfig.architecture = pe.find_architecture(fh)
|
|
810
806
|
# Return the first found beacon config.
|
|
811
807
|
return bconfig
|
|
808
|
+
|
|
809
|
+
# Try finding Beacon config protected with Guardrails
|
|
810
|
+
try:
|
|
811
|
+
fxor = XorEncodedFile.from_file(fobj)
|
|
812
|
+
except ValueError:
|
|
813
|
+
fxor = fobj
|
|
814
|
+
for grconfig in iter_guardrail_configs_with_beacon(fxor):
|
|
815
|
+
if not grconfig.unmasked_beacon_config:
|
|
816
|
+
continue
|
|
817
|
+
bconfig = cls(grconfig.unmasked_beacon_config)
|
|
818
|
+
bconfig.guardrails = grconfig
|
|
819
|
+
bconfig.xorkey = grconfig.beacon_xor_key
|
|
820
|
+
bconfig.pe_compile_stamp, bconfig.pe_export_stamp = pe.find_compile_stamps(fxor)
|
|
821
|
+
bconfig.architecture = pe.find_architecture(fxor)
|
|
822
|
+
return bconfig
|
|
823
|
+
|
|
812
824
|
raise ValueError("No valid Beacon configuration found")
|
|
813
825
|
|
|
814
826
|
@classmethod
|
|
@@ -1108,7 +1120,7 @@ def build_parser():
|
|
|
1108
1120
|
import argparse
|
|
1109
1121
|
|
|
1110
1122
|
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
|
1111
|
-
parser.add_argument("input", metavar="FILE", help="Beacon to dump")
|
|
1123
|
+
parser.add_argument("input", metavar="FILE", nargs="+", help="Beacon to dump")
|
|
1112
1124
|
parser.add_argument(
|
|
1113
1125
|
"-x",
|
|
1114
1126
|
"--xorkey",
|
|
@@ -1116,10 +1128,9 @@ def build_parser():
|
|
|
1116
1128
|
help="override default xor key(s) (default: -x 0x69 -x 0x2e -x 0x00)",
|
|
1117
1129
|
)
|
|
1118
1130
|
parser.add_argument(
|
|
1119
|
-
"-
|
|
1120
|
-
"--all",
|
|
1131
|
+
"--default-xor-keys-only",
|
|
1121
1132
|
action="store_true",
|
|
1122
|
-
help="try
|
|
1133
|
+
help="only try the default xor keys instead of all possible ones",
|
|
1123
1134
|
)
|
|
1124
1135
|
parser.add_argument(
|
|
1125
1136
|
"-t",
|
|
@@ -1150,63 +1161,71 @@ def main():
|
|
|
1150
1161
|
level = levels[min(len(levels) - 1, args.verbose)]
|
|
1151
1162
|
logging.basicConfig(
|
|
1152
1163
|
level=level,
|
|
1153
|
-
|
|
1154
|
-
format="%(asctime)s %(name)s %(message)s",
|
|
1164
|
+
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
|
|
1155
1165
|
)
|
|
1156
1166
|
|
|
1157
1167
|
xor_keys = None
|
|
1158
1168
|
if args.xorkey:
|
|
1159
1169
|
xor_keys = tuple(utils.pack_be(int(x, 0)) for x in args.xorkey)
|
|
1160
1170
|
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
"
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1171
|
+
dumped = False
|
|
1172
|
+
for fname in args.input:
|
|
1173
|
+
logging.info("Processing: %r", fname)
|
|
1174
|
+
try:
|
|
1175
|
+
if fname in ("-", "/dev/stdin"):
|
|
1176
|
+
with io.BytesIO(sys.stdin.buffer.read()) as fin:
|
|
1177
|
+
config = BeaconConfig.from_file(fin, xor_keys=xor_keys, all_xor_keys=not args.default_xor_keys_only)
|
|
1178
|
+
else:
|
|
1179
|
+
config = BeaconConfig.from_path(fname, xor_keys=xor_keys, all_xor_keys=not args.default_xor_keys_only)
|
|
1180
|
+
except ValueError:
|
|
1181
|
+
print(f"{fname}: No beacon configuration found.", file=sys.stderr)
|
|
1182
|
+
continue
|
|
1183
|
+
|
|
1184
|
+
dumped = True
|
|
1185
|
+
if args.type == "raw":
|
|
1186
|
+
for setting in config.settings_tuple:
|
|
1187
|
+
print(setting)
|
|
1188
|
+
elif args.type == "dumpstruct":
|
|
1189
|
+
cstruct.hexdump(config.config_block)
|
|
1190
|
+
print("-----")
|
|
1191
|
+
for setting in config.settings_tuple:
|
|
1192
|
+
cstruct.dumpstruct(setting)
|
|
1193
|
+
print("-" * 10)
|
|
1194
|
+
elif args.type == "normal":
|
|
1195
|
+
settings = config.settings
|
|
1196
|
+
for setting, value in settings.items():
|
|
1197
|
+
print(f"{setting} = {value!r}")
|
|
1198
|
+
if args.verbose >= 1:
|
|
1199
|
+
print("-" * 50)
|
|
1200
|
+
print(
|
|
1201
|
+
"pe_export_stamp = {}, {}, {}".format(
|
|
1202
|
+
config.pe_export_stamp,
|
|
1203
|
+
hex(config.pe_export_stamp),
|
|
1204
|
+
time.ctime(config.pe_export_stamp),
|
|
1205
|
+
)
|
|
1191
1206
|
)
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1207
|
+
print(
|
|
1208
|
+
"pe_compile_stamp = {}, {}, {}".format(
|
|
1209
|
+
config.pe_compile_stamp,
|
|
1210
|
+
hex(config.pe_compile_stamp),
|
|
1211
|
+
time.ctime(config.pe_compile_stamp),
|
|
1212
|
+
)
|
|
1198
1213
|
)
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1214
|
+
print(
|
|
1215
|
+
"max_setting_enum = {} - {}".format(
|
|
1216
|
+
config.max_setting_enum,
|
|
1217
|
+
BeaconSetting(config.max_setting_enum),
|
|
1218
|
+
)
|
|
1204
1219
|
)
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1220
|
+
print("beacon_version =", config.version)
|
|
1221
|
+
if config.guardrails:
|
|
1222
|
+
print("guardrail payload xor key =", config.guardrails.payload_xor_key)
|
|
1223
|
+
print("guardrail options =", [s.option for s in config.guardrails.settings])
|
|
1224
|
+
elif args.type == "c2profile":
|
|
1225
|
+
profile = c2profile.C2Profile.from_beacon_config(config)
|
|
1226
|
+
print(profile.as_text())
|
|
1227
|
+
|
|
1228
|
+
return 0 if dumped else 1
|
|
1210
1229
|
|
|
1211
1230
|
|
|
1212
1231
|
if __name__ == "__main__":
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module is responsible for finding and recovering Beacon Guardrails configuration from Cobalt Strike payloads.
|
|
3
|
+
Guardrails is an additional layer of protection to the beacon config by using environmental keying (`T1480`_).
|
|
4
|
+
|
|
5
|
+
Beacon Guardrails was introduced in Cobalt Strike 4.8:
|
|
6
|
+
|
|
7
|
+
- https://www.cobaltstrike.com/blog/cobalt-strike-4-8-system-call-me-maybe
|
|
8
|
+
|
|
9
|
+
Other research on Beacon Guardrails:
|
|
10
|
+
|
|
11
|
+
- https://itea.org/journals/volume-45-3/cobalt-strike-cyber-assessment-challenge/
|
|
12
|
+
|
|
13
|
+
.. _T1480: https://attack.mitre.org/techniques/T1480/
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import collections
|
|
19
|
+
import functools
|
|
20
|
+
import io
|
|
21
|
+
import logging
|
|
22
|
+
from dataclasses import dataclass
|
|
23
|
+
from typing import TYPE_CHECKING, BinaryIO
|
|
24
|
+
|
|
25
|
+
from dissect.cobaltstrike.utils import grouper, u32be, xor
|
|
26
|
+
from dissect.cstruct import cstruct
|
|
27
|
+
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
from collections.abc import Iterator
|
|
30
|
+
|
|
31
|
+
log = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
C_GUARDRAILS_DEF = """
|
|
34
|
+
enum GuardOption: uint16 {
|
|
35
|
+
GUARD_USER = 5,
|
|
36
|
+
GUARD_COMPUTER = 6,
|
|
37
|
+
GUARD_DOMAIN = 7,
|
|
38
|
+
GUARD_LOCAL_IP = 8,
|
|
39
|
+
GUARD_PAYLOAD_CHECKSUM = 9,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
enum SettingsType: uint16 {
|
|
43
|
+
TYPE_NONE = 0,
|
|
44
|
+
TYPE_SHORT = 1,
|
|
45
|
+
TYPE_INT = 2,
|
|
46
|
+
TYPE_PTR = 3,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
struct GuardrailSetting {
|
|
50
|
+
GuardOption option; // uint16
|
|
51
|
+
SettingsType type; // uint16
|
|
52
|
+
uint16 length; // uint16
|
|
53
|
+
char value[length];
|
|
54
|
+
};
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
BEACON_CONFIG_PATCH_SIZE = 6144
|
|
58
|
+
GUARD_PATCH_SIZE = 2048
|
|
59
|
+
|
|
60
|
+
GUARD_CONFIG_STARTS = [
|
|
61
|
+
b"\x00\x05\x00\x01\x00\x02", # GUARD_USER
|
|
62
|
+
b"\x00\x06\x00\x01\x00\x02", # GUARD_COMPUTER
|
|
63
|
+
b"\x00\x07\x00\x01\x00\x02", # GUARD_DOMAIN
|
|
64
|
+
b"\x00\x08\x00\x02\x00\x04", # GUARD_LOCAL_IP
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
c_guardrails = cstruct(endian=">").load(C_GUARDRAILS_DEF)
|
|
68
|
+
GuardrailSetting = c_guardrails.GuardrailSetting
|
|
69
|
+
GuardOption = c_guardrails.GuardOption
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass
|
|
73
|
+
class GuardrailMetadata:
|
|
74
|
+
"""Class for holding Guardrail related data"""
|
|
75
|
+
|
|
76
|
+
beacon_config_offset: int
|
|
77
|
+
""" Offset of the beacon configuration in the payload """
|
|
78
|
+
guard_config_offset: int
|
|
79
|
+
""" Offset of the guardrail configuration in the payload """
|
|
80
|
+
masked_beacon_config: bytes
|
|
81
|
+
""" Masked raw beacon configuration """
|
|
82
|
+
masked_guard_config: bytes
|
|
83
|
+
""" Masked raw guardrail configuration """
|
|
84
|
+
beacon_xor_key: bytes
|
|
85
|
+
""" Single byte XOR key used to mask the beacon configuration. (0x2e by default unless modified beacon) """
|
|
86
|
+
guardrail_xor_key: bytes
|
|
87
|
+
""" Single byte XOR key used to unmask the guardrail configuration (0x8a by default unless modified beacon) """
|
|
88
|
+
unmasked_guard_config: bytes
|
|
89
|
+
""" Unmasked guardrail configuration """
|
|
90
|
+
checksum: int
|
|
91
|
+
""" Extracted payload checksum from guardrail configuration. This is used to validate the beacon configuration """
|
|
92
|
+
payload_xor_key: bytes | None
|
|
93
|
+
""" XOR key used to unmask the guarded beacon configuration. This is the environmental key """
|
|
94
|
+
unmasked_beacon_config: bytes
|
|
95
|
+
""" Unmasked beacon configuration """
|
|
96
|
+
settings: list[GuardrailSetting]
|
|
97
|
+
""" List of guardrail settings """
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def iter_guardrail_configs(fh: BinaryIO, xorkey: bytes = b"\x8a") -> Iterator[GuardrailMetadata]:
|
|
101
|
+
xorred_guardconfig_starts = [xor(x, xorkey) for x in GUARD_CONFIG_STARTS]
|
|
102
|
+
size = len(xorred_guardconfig_starts[0])
|
|
103
|
+
offset = 0
|
|
104
|
+
while True:
|
|
105
|
+
fh.seek(offset)
|
|
106
|
+
block = fh.read(size * 2)
|
|
107
|
+
if not block:
|
|
108
|
+
break
|
|
109
|
+
a, b = block[:size], block[size:]
|
|
110
|
+
if xor(a[::-1], b) in xorred_guardconfig_starts:
|
|
111
|
+
log.info("Found guardrail config at offset: %u in %r", offset, fh)
|
|
112
|
+
guard_config_offset = offset + 6
|
|
113
|
+
beacon_config_offset = guard_config_offset - BEACON_CONFIG_PATCH_SIZE
|
|
114
|
+
fh.seek(beacon_config_offset)
|
|
115
|
+
masked_beacon_config = fh.read(BEACON_CONFIG_PATCH_SIZE)
|
|
116
|
+
masked_guard_config = fh.read(GUARD_PATCH_SIZE)
|
|
117
|
+
unmasked_guard_config = xor(xor(masked_guard_config, masked_beacon_config[::-1]), xorkey)
|
|
118
|
+
|
|
119
|
+
fh_guard = io.BufferedReader(io.BytesIO(unmasked_guard_config))
|
|
120
|
+
checksum = 0
|
|
121
|
+
settings: list[GuardrailSetting] = []
|
|
122
|
+
while True:
|
|
123
|
+
if fh_guard.peek(2)[:2] == b"\x00\x00":
|
|
124
|
+
break
|
|
125
|
+
setting = GuardrailSetting(fh_guard)
|
|
126
|
+
settings.append(setting)
|
|
127
|
+
log.debug(setting)
|
|
128
|
+
if setting.option == GuardOption.GUARD_PAYLOAD_CHECKSUM:
|
|
129
|
+
checksum = u32be(setting.value)
|
|
130
|
+
log.debug("%s = 0x%08x", setting.option.name, checksum)
|
|
131
|
+
|
|
132
|
+
yield GuardrailMetadata(
|
|
133
|
+
beacon_config_offset=beacon_config_offset,
|
|
134
|
+
guard_config_offset=guard_config_offset,
|
|
135
|
+
checksum=checksum,
|
|
136
|
+
masked_guard_config=masked_guard_config,
|
|
137
|
+
masked_beacon_config=masked_beacon_config,
|
|
138
|
+
unmasked_guard_config=unmasked_guard_config,
|
|
139
|
+
guardrail_xor_key=xorkey,
|
|
140
|
+
beacon_xor_key=b"\x2e", # we currently only support the XOR default key
|
|
141
|
+
payload_xor_key=None,
|
|
142
|
+
unmasked_beacon_config=None,
|
|
143
|
+
settings=settings,
|
|
144
|
+
)
|
|
145
|
+
offset += 1
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def find_xor_key_candidates(fh: BinaryIO) -> Iterator[bytes]:
|
|
149
|
+
for keylen in range(2, 257):
|
|
150
|
+
fh.seek(0)
|
|
151
|
+
counter = collections.Counter()
|
|
152
|
+
for chunk in iter(functools.partial(fh.read, io.DEFAULT_BUFFER_SIZE), b""):
|
|
153
|
+
grams = grouper(chunk, n=keylen, fillvalue=0)
|
|
154
|
+
counter.update(bytes(gram) for gram in grams)
|
|
155
|
+
|
|
156
|
+
first_count = 0
|
|
157
|
+
for key, count in counter.most_common(2):
|
|
158
|
+
if count >= first_count:
|
|
159
|
+
first_count = count
|
|
160
|
+
yield key
|
|
161
|
+
else:
|
|
162
|
+
break
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def payload_checksum(data: bytes) -> int:
|
|
166
|
+
n = 0
|
|
167
|
+
for i in range(len(data)):
|
|
168
|
+
n = (n + (data[i] & 0xFF) * (i % 3 + 1)) % 99999999
|
|
169
|
+
return n
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def iter_guardrail_configs_with_beacon(fh: BinaryIO) -> Iterator[GuardrailMetadata]:
|
|
173
|
+
for grconfig in iter_guardrail_configs(fh):
|
|
174
|
+
|
|
175
|
+
# Unmask the beacon config, static single byte xor key should be 0x2E unless modified beacon
|
|
176
|
+
# The beacon config is still masked with the environmental key
|
|
177
|
+
grconfig.beacon_xor_key = b"\x2e" # we currently only support the XOR default key
|
|
178
|
+
guarded_config = xor(grconfig.masked_beacon_config, grconfig.beacon_xor_key)
|
|
179
|
+
|
|
180
|
+
for xorkey in find_xor_key_candidates(io.BytesIO(guarded_config)):
|
|
181
|
+
unguarded = xor(guarded_config, xorkey)
|
|
182
|
+
|
|
183
|
+
checksum = payload_checksum(unguarded) + 1
|
|
184
|
+
log.debug("payload checksum: 0x%08x for xorkey: %r", checksum, xorkey)
|
|
185
|
+
|
|
186
|
+
if grconfig.checksum == checksum:
|
|
187
|
+
log.info("Found guardrail payload xorkey: %r", xorkey)
|
|
188
|
+
grconfig.payload_xor_key = xorkey
|
|
189
|
+
grconfig.unmasked_beacon_config = unguarded
|
|
190
|
+
yield grconfig
|
|
191
|
+
break
|
|
192
|
+
else:
|
|
193
|
+
# No valid xor key found, so not able to unmask the beacon config
|
|
194
|
+
# but we can still return the guardrail config
|
|
195
|
+
yield grconfig
|
{dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/dissect/cobaltstrike/utils.py
RENAMED
|
@@ -4,6 +4,7 @@ This module contains generic helper functions used by ``dissect.cobaltstrike``.
|
|
|
4
4
|
|
|
5
5
|
import errno
|
|
6
6
|
import io
|
|
7
|
+
import itertools
|
|
7
8
|
import os
|
|
8
9
|
import random
|
|
9
10
|
import re
|
|
@@ -280,3 +281,10 @@ class LRUDict(OrderedDict):
|
|
|
280
281
|
if len(self) > self.maxsize:
|
|
281
282
|
oldest = next(iter(self))
|
|
282
283
|
del self[oldest]
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def grouper(iterable, n, fillvalue=None):
|
|
287
|
+
"Collect data into fixed-length chunks or blocks"
|
|
288
|
+
# grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx"
|
|
289
|
+
args = [iter(iterable)] * n
|
|
290
|
+
return itertools.zip_longest(*args, fillvalue=fillvalue)
|
{dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/dissect/cobaltstrike/version.py
RENAMED
|
@@ -80,11 +80,13 @@ PE_EXPORT_STAMP_TO_VERSION: Dict[int, str] = {
|
|
|
80
80
|
0x63EE056C: "Cobalt Strike 4.8 (Feb 28, 2023)",
|
|
81
81
|
0x63EE0587: "Cobalt Strike 4.8 (Feb 28, 2023)",
|
|
82
82
|
0x64F88C5E: "Cobalt Strike 4.9 (Sep 19, 2023)",
|
|
83
|
+
0x64F88C7E: "Cobalt Strike 4.9 (Sep 19, 2023)",
|
|
83
84
|
0x64F88C9E: "Cobalt Strike 4.9 (Sep 19, 2023)",
|
|
84
85
|
0x64F88CDE: "Cobalt Strike 4.9 (Sep 19, 2023)",
|
|
85
86
|
0x6691500F: "Cobalt Strike 4.10 (Jul 16, 2024)",
|
|
86
87
|
0x66915020: "Cobalt Strike 4.10 (Jul 16, 2024)",
|
|
87
88
|
0x66915022: "Cobalt Strike 4.10 (Jul 16, 2024)",
|
|
89
|
+
0x66915024: "Cobalt Strike 4.10 (Jul 16, 2024)",
|
|
88
90
|
}
|
|
89
91
|
""" PE export timestamp to Cobalt Strike version mapping """
|
|
90
92
|
|
|
@@ -19,6 +19,8 @@ beacons = {
|
|
|
19
19
|
"c2test_beacon": "37882262c9b5e971067fd989b26afe28.bin.zip",
|
|
20
20
|
# beacon with unicode in domain
|
|
21
21
|
"punycode_beacon": "5a197a8bb628a2555f5a86c51b85abd7.bin.zip",
|
|
22
|
+
# guardrails protected beacon
|
|
23
|
+
"guardrails_beacon": "124552cf674b362e0c916ab79b9e7a56.bin.zip",
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
|
|
@@ -323,3 +323,45 @@ def test_beacon_setting_unknown_enum():
|
|
|
323
323
|
config = beacon.BeaconConfig(data)
|
|
324
324
|
assert None not in config.settings
|
|
325
325
|
assert dict(config.settings) == {"BeaconSetting_6969": b"foo"}
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def test_beacon_dump_multiple_files(beacon_x86_path, beacon_x64_path):
|
|
329
|
+
proc = subprocess.run(
|
|
330
|
+
[sys.executable, "-m", "dissect.cobaltstrike.beacon", str(beacon_x86_path), str(beacon_x64_path)],
|
|
331
|
+
capture_output=True,
|
|
332
|
+
)
|
|
333
|
+
proc.check_returncode()
|
|
334
|
+
assert "9b9e85b111d9bef8d599905a06be0d207c388c4acaab8e74a01c04406fe26309" in proc.stdout.decode()
|
|
335
|
+
assert "71fab2149cbdce552f00e6d75372494d3f7755d366fd6849a6d5c9e0f73bc40f" in proc.stdout.decode()
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def test_beacon_dump_default_xor_keys_only(beacon_custom_xorkey_path):
|
|
339
|
+
# default behavior is to try all xor keys, and we should find a valid beacon
|
|
340
|
+
proc = subprocess.run(
|
|
341
|
+
[
|
|
342
|
+
sys.executable,
|
|
343
|
+
"-m",
|
|
344
|
+
"dissect.cobaltstrike.beacon",
|
|
345
|
+
str(beacon_custom_xorkey_path),
|
|
346
|
+
],
|
|
347
|
+
capture_output=True,
|
|
348
|
+
)
|
|
349
|
+
assert proc.returncode == 0
|
|
350
|
+
assert b"SETTING_PUBKEY = '36aff0b273cb7aa704e4219ad3be78defcc8c1d7ecb779d55f438e82c7138673'" in proc.stdout
|
|
351
|
+
proc.check_returncode()
|
|
352
|
+
|
|
353
|
+
# when we enable --default-xor-keys-only, we should not find a valid beacon
|
|
354
|
+
proc = subprocess.run(
|
|
355
|
+
[
|
|
356
|
+
sys.executable,
|
|
357
|
+
"-m",
|
|
358
|
+
"dissect.cobaltstrike.beacon",
|
|
359
|
+
str(beacon_custom_xorkey_path),
|
|
360
|
+
"--default-xor-keys-only",
|
|
361
|
+
],
|
|
362
|
+
capture_output=True,
|
|
363
|
+
)
|
|
364
|
+
assert proc.returncode == 1
|
|
365
|
+
assert b"No beacon configuration found" in proc.stderr
|
|
366
|
+
with pytest.raises(subprocess.CalledProcessError):
|
|
367
|
+
proc.check_returncode()
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from dissect.cobaltstrike.beacon import BeaconConfig
|
|
7
|
+
from dissect.cobaltstrike.guardrails import GUARD_CONFIG_STARTS, c_guardrails
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_guard_config_start_size() -> None:
|
|
11
|
+
"""Test that all GUARD_CONFIG_STARTS have the same length."""
|
|
12
|
+
sizes = map(len, GUARD_CONFIG_STARTS)
|
|
13
|
+
assert len(set(sizes)) == 1
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.mark.parametrize(
|
|
17
|
+
"guard_option,settings_type,length",
|
|
18
|
+
[
|
|
19
|
+
(c_guardrails.GuardOption.GUARD_USER, c_guardrails.SettingsType.TYPE_SHORT, 2),
|
|
20
|
+
(c_guardrails.GuardOption.GUARD_COMPUTER, c_guardrails.SettingsType.TYPE_SHORT, 2),
|
|
21
|
+
(c_guardrails.GuardOption.GUARD_DOMAIN, c_guardrails.SettingsType.TYPE_SHORT, 2),
|
|
22
|
+
(c_guardrails.GuardOption.GUARD_LOCAL_IP, c_guardrails.SettingsType.TYPE_INT, 4),
|
|
23
|
+
],
|
|
24
|
+
)
|
|
25
|
+
def test_guard_config_start_settings(guard_option, settings_type, length) -> None:
|
|
26
|
+
"""Test correctness of the known Guardrail start bytes by constructing the GuardrailSetting."""
|
|
27
|
+
guard_option_user = c_guardrails.GuardrailSetting(
|
|
28
|
+
option=guard_option,
|
|
29
|
+
type=settings_type,
|
|
30
|
+
length=length,
|
|
31
|
+
value=b"", # ignored
|
|
32
|
+
).dumps()
|
|
33
|
+
assert guard_option_user in GUARD_CONFIG_STARTS
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_beacon_dump_guardrails(guardrails_beacon_path):
|
|
37
|
+
proc = subprocess.run(
|
|
38
|
+
[sys.executable, "-m", "dissect.cobaltstrike.beacon", "-v", "-t", "normal", str(guardrails_beacon_path)],
|
|
39
|
+
capture_output=True,
|
|
40
|
+
)
|
|
41
|
+
proc.check_returncode()
|
|
42
|
+
stdout = proc.stdout
|
|
43
|
+
stderr = proc.stderr
|
|
44
|
+
|
|
45
|
+
assert b"Found guardrail payload xorkey: b'desktop-r4vgq8o'" in stderr
|
|
46
|
+
assert b"guardrail payload xor key = b'desktop-r4vgq8o'" in stdout
|
|
47
|
+
assert b"guardrail options = [<GuardOption.GUARD_COMPUTER: 6>, <GuardOption.GUARD_PAYLOAD_CHECKSUM: 9>]" in stdout
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_beacon_guardrails_metadata(guardrails_beacon_file, beacon_x64_file):
|
|
51
|
+
beacon = BeaconConfig.from_file(guardrails_beacon_file)
|
|
52
|
+
assert beacon.guardrails is not None
|
|
53
|
+
assert beacon.guardrails.payload_xor_key == b"desktop-r4vgq8o"
|
|
54
|
+
assert beacon.guardrails.settings[0].option == c_guardrails.GuardOption.GUARD_COMPUTER
|
|
55
|
+
assert beacon.guardrails.settings[1].option == c_guardrails.GuardOption.GUARD_PAYLOAD_CHECKSUM
|
|
56
|
+
assert beacon.guardrails.checksum == 0xA5AD1
|
|
57
|
+
assert beacon.guardrails.guardrail_xor_key == b"\x8a"
|
|
58
|
+
|
|
59
|
+
beacon = BeaconConfig.from_file(beacon_x64_file)
|
|
60
|
+
assert beacon.guardrails is None
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
# file generated by setuptools_scm
|
|
2
|
-
# don't change, don't track in version control
|
|
3
|
-
TYPE_CHECKING = False
|
|
4
|
-
if TYPE_CHECKING:
|
|
5
|
-
from typing import Tuple, Union
|
|
6
|
-
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
7
|
-
else:
|
|
8
|
-
VERSION_TUPLE = object
|
|
9
|
-
|
|
10
|
-
version: str
|
|
11
|
-
__version__: str
|
|
12
|
-
__version_tuple__: VERSION_TUPLE
|
|
13
|
-
version_tuple: VERSION_TUPLE
|
|
14
|
-
|
|
15
|
-
__version__ = version = '1.2.1.dev9'
|
|
16
|
-
__version_tuple__ = version_tuple = (1, 2, 1, 'dev9')
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/.pre-commit-config.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/dissect/cobaltstrike/c2.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/dissect/cobaltstrike/c_c2.py
RENAMED
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/dissect/cobaltstrike/client.py
RENAMED
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/dissect/cobaltstrike/pcap.py
RENAMED
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/dissect/cobaltstrike/pe.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/docs/c2profile-grammar.rst
RENAMED
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/docs/codespell-ignore-words.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/docs/images/beacon-client.png
RENAMED
|
File without changes
|
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/docs/images/teamserver_ls.png
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/docs/scripts/example_client.rst
RENAMED
|
File without changes
|
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/docs/structures/c2_def.rst
RENAMED
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/docs/structures/cs_def.rst
RENAMED
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/docs/structures/index.rst
RENAMED
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/docs/structures/pe_def.rst
RENAMED
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/docs/tools/beacon-artifact.rst
RENAMED
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/docs/tools/beacon-client.rst
RENAMED
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/docs/tools/beacon-dump.rst
RENAMED
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/docs/tools/beacon-pcap.rst
RENAMED
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/docs/tools/beacon-xordecode.rst
RENAMED
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/docs/tools/c2profile-dump.rst
RENAMED
|
File without changes
|
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/docs/tutorials/index.rst
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/scripts/checksum8-accesslogs.py
RENAMED
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/scripts/dump_beacon_keys.py
RENAMED
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/scripts/example_client.py
RENAMED
|
File without changes
|
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/tests/test_c2profile.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev9 → dissect_cobaltstrike-1.2.1.dev10}/tests/test_xordecode.py
RENAMED
|
File without changes
|
|
File without changes
|