dissect.cobaltstrike 1.2.1.dev8__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.dev8 → 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.dev8 → dissect_cobaltstrike-1.2.1.dev10}/dissect/cobaltstrike/beacon.py +80 -60
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/dissect/cobaltstrike/c2profile.lark +5 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/dissect/cobaltstrike/c2profile.py +10 -0
- dissect_cobaltstrike-1.2.1.dev10/dissect/cobaltstrike/guardrails.py +195 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/dissect/cobaltstrike/utils.py +8 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/dissect/cobaltstrike/version.py +2 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/tests/conftest.py +2 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/tests/test_beacon.py +42 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/tests/test_c2profile.py +22 -0
- dissect_cobaltstrike-1.2.1.dev10/tests/test_guardrails.py +60 -0
- dissect_cobaltstrike-1.2.1.dev8/dissect/cobaltstrike/_version.py +0 -16
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/.github/workflows/dissect-ci.yml +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/.gitignore +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/.lfs-assets-id +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/.pre-commit-config.yaml +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/.readthedocs.yml +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/LICENSE +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/README.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/codecov.yml +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/dissect/cobaltstrike/__init__.py +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/dissect/cobaltstrike/artifact.py +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/dissect/cobaltstrike/c2.py +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/dissect/cobaltstrike/c_c2.py +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/dissect/cobaltstrike/client.py +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/dissect/cobaltstrike/pcap.py +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/dissect/cobaltstrike/pe.py +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/dissect/cobaltstrike/xordecode.py +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/docs/Makefile +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/docs/c2profile-grammar.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/docs/codespell-ignore-words.txt +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/docs/conf.py +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/docs/examples.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/docs/images/beacon-client.png +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/docs/images/teamserver-echoclient.png +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/docs/images/teamserver_ls.png +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/docs/images/vt-cobaltstrike-43-zip.png +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/docs/index.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/docs/installation.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/docs/license.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/docs/scripts/checksum8-accesslogs.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/docs/scripts/dump_beacon_keys.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/docs/scripts/example_client.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/docs/scripts/index.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/docs/structures/c2_def.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/docs/structures/cs_def.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/docs/structures/index.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/docs/structures/pe_def.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/docs/tools/beacon-artifact.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/docs/tools/beacon-client.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/docs/tools/beacon-dump.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/docs/tools/beacon-pcap.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/docs/tools/beacon-xordecode.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/docs/tools/c2profile-dump.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/docs/tutorials/decrypt_cobaltstrike_pcaps.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/docs/tutorials/index.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/docs/tutorials/minimal_beacon_client.rst +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/pyproject.toml +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/scripts/checksum8-accesslogs.py +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/scripts/dump_beacon_keys.py +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/scripts/example_client.py +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/tests/test_c2.py +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/tests/test_client.py +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/tests/test_pe.py +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/tests/test_utils.py +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/tests/test_version.py +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/tests/test_xordecode.py +0 -0
- {dissect_cobaltstrike-1.2.1.dev8 → 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.dev8 → 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,
|
|
@@ -175,6 +176,7 @@ enum BeaconSetting: uint16 {
|
|
|
175
176
|
SETTING_DATA_STORE_SIZE = 76,
|
|
176
177
|
|
|
177
178
|
// CobaltStrike version >= 4.10 (Jul 16, 2024)
|
|
179
|
+
SETTING_HTTP_DATA_REQUIRED = 77,
|
|
178
180
|
SETTING_BEACON_GATE = 78,
|
|
179
181
|
};
|
|
180
182
|
|
|
@@ -463,13 +465,6 @@ def iter_settings(fobj: Union[bytes, BinaryIO]) -> Iterator["Setting"]:
|
|
|
463
465
|
yield setting
|
|
464
466
|
|
|
465
467
|
|
|
466
|
-
def grouper(iterable, n, fillvalue=None):
|
|
467
|
-
"Collect data into fixed-length chunks or blocks"
|
|
468
|
-
# grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx"
|
|
469
|
-
args = [iter(iterable)] * n
|
|
470
|
-
return itertools.zip_longest(*args, fillvalue=fillvalue)
|
|
471
|
-
|
|
472
|
-
|
|
473
468
|
def parse_recover_binary(program: bytes) -> List[Tuple[str, Union[int, bool]]]:
|
|
474
469
|
"""Parse ``SETTING_C2_RECOVER`` (`.http-get.server.output`) data"""
|
|
475
470
|
rsteps: List[Tuple[str, Union[int, bool]]] = []
|
|
@@ -772,6 +767,8 @@ class BeaconConfig:
|
|
|
772
767
|
""" PE compile timestamp, ``None`` if unknown. """
|
|
773
768
|
self.architecture: Optional[str] = None
|
|
774
769
|
""" PE architecture, ``"x86"`` or ``"x64"`` and ``None`` if unknown. """
|
|
770
|
+
self.guardrails: Optional[GuardrailMetadata] = None
|
|
771
|
+
""" Guardrails metadata, ``None`` if not available. """
|
|
775
772
|
|
|
776
773
|
# Used for caching
|
|
777
774
|
self._settings: Optional[Mapping[str, Any]] = None
|
|
@@ -808,6 +805,22 @@ class BeaconConfig:
|
|
|
808
805
|
bconfig.architecture = pe.find_architecture(fh)
|
|
809
806
|
# Return the first found beacon config.
|
|
810
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
|
+
|
|
811
824
|
raise ValueError("No valid Beacon configuration found")
|
|
812
825
|
|
|
813
826
|
@classmethod
|
|
@@ -1107,7 +1120,7 @@ def build_parser():
|
|
|
1107
1120
|
import argparse
|
|
1108
1121
|
|
|
1109
1122
|
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
|
1110
|
-
parser.add_argument("input", metavar="FILE", help="Beacon to dump")
|
|
1123
|
+
parser.add_argument("input", metavar="FILE", nargs="+", help="Beacon to dump")
|
|
1111
1124
|
parser.add_argument(
|
|
1112
1125
|
"-x",
|
|
1113
1126
|
"--xorkey",
|
|
@@ -1115,10 +1128,9 @@ def build_parser():
|
|
|
1115
1128
|
help="override default xor key(s) (default: -x 0x69 -x 0x2e -x 0x00)",
|
|
1116
1129
|
)
|
|
1117
1130
|
parser.add_argument(
|
|
1118
|
-
"-
|
|
1119
|
-
"--all",
|
|
1131
|
+
"--default-xor-keys-only",
|
|
1120
1132
|
action="store_true",
|
|
1121
|
-
help="try
|
|
1133
|
+
help="only try the default xor keys instead of all possible ones",
|
|
1122
1134
|
)
|
|
1123
1135
|
parser.add_argument(
|
|
1124
1136
|
"-t",
|
|
@@ -1149,63 +1161,71 @@ def main():
|
|
|
1149
1161
|
level = levels[min(len(levels) - 1, args.verbose)]
|
|
1150
1162
|
logging.basicConfig(
|
|
1151
1163
|
level=level,
|
|
1152
|
-
|
|
1153
|
-
format="%(asctime)s %(name)s %(message)s",
|
|
1164
|
+
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
|
|
1154
1165
|
)
|
|
1155
1166
|
|
|
1156
1167
|
xor_keys = None
|
|
1157
1168
|
if args.xorkey:
|
|
1158
1169
|
xor_keys = tuple(utils.pack_be(int(x, 0)) for x in args.xorkey)
|
|
1159
1170
|
|
|
1160
|
-
|
|
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
|
-
|
|
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
|
+
)
|
|
1190
1206
|
)
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
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
|
+
)
|
|
1197
1213
|
)
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1214
|
+
print(
|
|
1215
|
+
"max_setting_enum = {} - {}".format(
|
|
1216
|
+
config.max_setting_enum,
|
|
1217
|
+
BeaconSetting(config.max_setting_enum),
|
|
1218
|
+
)
|
|
1203
1219
|
)
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
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
|
|
1209
1229
|
|
|
1210
1230
|
|
|
1211
1231
|
if __name__ == "__main__":
|
|
@@ -11,6 +11,7 @@ start: value*
|
|
|
11
11
|
| "process-inject" "{" process_inject_options* "}" -> process_inject
|
|
12
12
|
| "post-ex" "{" postex_options* "}" -> post_ex
|
|
13
13
|
| "dns-beacon" "{" dns_beacon_options* "}" -> dns_beacon
|
|
14
|
+
| "http-beacon" "{" http_beacon_options* "}" -> http_beacon
|
|
14
15
|
|
|
15
16
|
OPTION: "sample_name"
|
|
16
17
|
| "data_jitter"
|
|
@@ -214,6 +215,10 @@ dns_beacon_options: "set" "dns_idle" string ";" -> dns_idle
|
|
|
214
215
|
| "set" "ns_response" string ";" -> ns_response
|
|
215
216
|
| "#" "dns_resolver" string ";" -> comment_dns_resolver
|
|
216
217
|
|
|
218
|
+
http_beacon_options: "set" "library" string ";" -> library // introduced in Cobalt Strike 4.9
|
|
219
|
+
| "set" "data_required" string ";" -> data_required // introduced in Cobalt Strike 4.10
|
|
220
|
+
| "set" "data_required_length" string ";" -> data_required_length // introduced in Cobalt Strike 4.10
|
|
221
|
+
|
|
217
222
|
header: string
|
|
218
223
|
string: STRING
|
|
219
224
|
variant: string
|
|
@@ -309,6 +309,12 @@ class DnsBeaconBlock(ConfigBlock):
|
|
|
309
309
|
__name__ = "dns_beacon"
|
|
310
310
|
|
|
311
311
|
|
|
312
|
+
class HttpBeaconBlock(ConfigBlock):
|
|
313
|
+
"""`.http-beacon` block"""
|
|
314
|
+
|
|
315
|
+
__name__ = "http_beacon"
|
|
316
|
+
|
|
317
|
+
|
|
312
318
|
class ExecuteOptionsBlock(ConfigBlock):
|
|
313
319
|
"""`.process-inject.execute` block"""
|
|
314
320
|
|
|
@@ -446,6 +452,7 @@ class C2Profile(ConfigBlock):
|
|
|
446
452
|
http_post_client = HttpOptionsBlock()
|
|
447
453
|
proc_inj = ProcessInjectBlock()
|
|
448
454
|
dns_beacon = DnsBeaconBlock()
|
|
455
|
+
http_beacon = HttpBeaconBlock()
|
|
449
456
|
# http_get_server = HttpOptionsBlock()
|
|
450
457
|
|
|
451
458
|
for setting, value in config.settings_by_index.items():
|
|
@@ -668,6 +675,8 @@ class C2Profile(ConfigBlock):
|
|
|
668
675
|
proc_inj.set_option("bof_allocator", value)
|
|
669
676
|
elif setting == BeaconSetting.SETTING_DATA_STORE_SIZE:
|
|
670
677
|
stage.set_option("data_store_size", value)
|
|
678
|
+
elif setting == BeaconSetting.SETTING_HTTP_DATA_REQUIRED and value:
|
|
679
|
+
http_beacon.set_option("data_required", "true")
|
|
671
680
|
elif setting == BeaconSetting.SETTING_BEACON_GATE and value:
|
|
672
681
|
block = BeaconGateBlock.from_beacon_gate_option_strings(value)
|
|
673
682
|
stage.set_config_block("beacon_gate", block)
|
|
@@ -681,6 +690,7 @@ class C2Profile(ConfigBlock):
|
|
|
681
690
|
profile.set_non_empty_config_block("stage", stage)
|
|
682
691
|
profile.set_non_empty_config_block("process_inject", proc_inj)
|
|
683
692
|
profile.set_non_empty_config_block("dns_beacon", dns_beacon)
|
|
693
|
+
profile.set_non_empty_config_block("http_beacon", http_beacon)
|
|
684
694
|
return profile
|
|
685
695
|
|
|
686
696
|
def __str__(self) -> str:
|
|
@@ -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.dev8 → 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.dev8 → 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()
|
{dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/tests/test_c2profile.py
RENAMED
|
@@ -395,3 +395,25 @@ def test_c2profile_beacon_gate():
|
|
|
395
395
|
bconfig = beacon.BeaconConfig(data)
|
|
396
396
|
profile = c2profile.C2Profile.from_beacon_config(bconfig)
|
|
397
397
|
assert "stage.beacon_gate" not in profile.properties
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
@pytest.mark.parametrize(
|
|
401
|
+
("http_data_required",),
|
|
402
|
+
[
|
|
403
|
+
(True,),
|
|
404
|
+
(False,),
|
|
405
|
+
],
|
|
406
|
+
)
|
|
407
|
+
def test_c2profile_http_data_required(http_data_required: bool):
|
|
408
|
+
data = beacon.Setting(
|
|
409
|
+
index=beacon.BeaconSetting.SETTING_HTTP_DATA_REQUIRED,
|
|
410
|
+
type=beacon.SettingsType.TYPE_SHORT,
|
|
411
|
+
length=0x2,
|
|
412
|
+
value=beacon.cs_struct.uint16(http_data_required).dumps(),
|
|
413
|
+
).dumps()
|
|
414
|
+
bconfig = beacon.BeaconConfig(data)
|
|
415
|
+
profile = c2profile.C2Profile.from_beacon_config(bconfig)
|
|
416
|
+
if not http_data_required:
|
|
417
|
+
assert "http-beacon.data_required" not in profile.properties
|
|
418
|
+
else:
|
|
419
|
+
assert profile.properties["http-beacon.data_required"] == ["true"]
|
|
@@ -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.dev8'
|
|
16
|
-
__version_tuple__ = version_tuple = (1, 2, 1, 'dev8')
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev8 → 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.dev8 → dissect_cobaltstrike-1.2.1.dev10}/dissect/cobaltstrike/c2.py
RENAMED
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/dissect/cobaltstrike/c_c2.py
RENAMED
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/dissect/cobaltstrike/client.py
RENAMED
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/dissect/cobaltstrike/pcap.py
RENAMED
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev8 → 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.dev8 → dissect_cobaltstrike-1.2.1.dev10}/docs/c2profile-grammar.rst
RENAMED
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev8 → 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.dev8 → dissect_cobaltstrike-1.2.1.dev10}/docs/images/beacon-client.png
RENAMED
|
File without changes
|
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev8 → 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.dev8 → dissect_cobaltstrike-1.2.1.dev10}/docs/scripts/example_client.rst
RENAMED
|
File without changes
|
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/docs/structures/c2_def.rst
RENAMED
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/docs/structures/cs_def.rst
RENAMED
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/docs/structures/index.rst
RENAMED
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/docs/structures/pe_def.rst
RENAMED
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/docs/tools/beacon-artifact.rst
RENAMED
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/docs/tools/beacon-client.rst
RENAMED
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/docs/tools/beacon-dump.rst
RENAMED
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/docs/tools/beacon-pcap.rst
RENAMED
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/docs/tools/beacon-xordecode.rst
RENAMED
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/docs/tools/c2profile-dump.rst
RENAMED
|
File without changes
|
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev8 → 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.dev8 → dissect_cobaltstrike-1.2.1.dev10}/scripts/checksum8-accesslogs.py
RENAMED
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/scripts/dump_beacon_keys.py
RENAMED
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/scripts/example_client.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dissect_cobaltstrike-1.2.1.dev8 → dissect_cobaltstrike-1.2.1.dev10}/tests/test_xordecode.py
RENAMED
|
File without changes
|
|
File without changes
|