CAPE-parsers 0.1.53__tar.gz → 0.1.55__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.
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/PKG-INFO +1 -1
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/community/KoiLoader.py +3 -2
- cape_parsers-0.1.55/cape_parsers/CAPE/core/NitroBunnyDownloader.py +168 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/core/Rhadamanthys.py +83 -56
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/pyproject.toml +1 -1
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/LICENSE +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/README.md +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/__init__.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/community/AgentTesla.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/community/Amadey.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/community/Arkei.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/community/AsyncRAT.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/community/AuroraStealer.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/community/Carbanak.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/community/CobaltStrikeBeacon.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/community/CobaltStrikeStager.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/community/DCRat.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/community/Fareit.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/community/LokiBot.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/community/Lumma.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/community/MonsterV2.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/community/MyKings.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/community/NanoCore.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/community/Nighthawk.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/community/Njrat.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/community/PhemedroneStealer.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/community/QuasarRAT.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/community/README.md +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/community/Snake.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/community/SparkRAT.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/community/Stealc.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/community/VenomRAT.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/community/WinosStager.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/community/XWorm.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/community/XenoRAT.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/community/__init__.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/core/AdaptixBeacon.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/core/AuraStealer.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/core/Azorult.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/core/BitPaymer.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/core/BlackDropper.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/core/Blister.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/core/BruteRatel.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/core/BumbleBee.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/core/DarkGate.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/core/DoppelPaymer.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/core/DridexLoader.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/core/Formbook.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/core/GuLoader.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/core/IcedID.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/core/IcedIDLoader.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/core/Latrodectus.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/core/Oyster.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/core/PikaBot.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/core/PlugX.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/core/QakBot.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/core/Quickbind.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/core/README.md +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/core/RedLine.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/core/Remcos.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/core/SmokeLoader.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/core/Socks5Systemz.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/core/SquirrelWaffle.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/core/Strrat.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/core/WarzoneRAT.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/core/Zloader.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/core/__init__.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/core/test_cape.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/RATDecoders/README.md +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/RATDecoders/__init__.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/RATDecoders/test_rats.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/__init__.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/deprecated/BackOffLoader.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/deprecated/BackOffPOS.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/deprecated/BlackNix.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/deprecated/BuerLoader.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/deprecated/ChChes.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/deprecated/Emotet.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/deprecated/Enfal.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/deprecated/EvilGrab.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/deprecated/Greame.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/deprecated/Hancitor.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/deprecated/HttpBrowser.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/deprecated/JavaDropper.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/deprecated/Nymaim.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/deprecated/Pandora.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/deprecated/PoisonIvy.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/deprecated/PredatorPain.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/deprecated/Punisher.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/deprecated/RCSession.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/deprecated/REvil.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/deprecated/RedLeaf.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/deprecated/Retefe.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/deprecated/Rozena.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/deprecated/SmallNet.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/deprecated/TSCookie.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/deprecated/TrickBot.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/deprecated/UrsnifV3.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/deprecated/_ShadowTech.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/deprecated/_VirusRat.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/deprecated/_jRat.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/deprecated/unrecom.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/deprecated/xRAT.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/malduck/LICENSE +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/malduck/README.md +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/malduck/__init__.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/malduck/test_malduck.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/mwcp/README.md +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/mwcp/__init__.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/mwcp/test_mwcp.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/utils/__init__.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/utils/aplib.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/utils/blzpack.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/utils/blzpack_lib.so +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/utils/dotnet_utils.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/utils/lznt1.py +0 -0
- {cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/utils/strings.py +0 -0
|
@@ -118,8 +118,9 @@ def extract_config(data):
|
|
|
118
118
|
|
|
119
119
|
encoded_payload = remove_nulls(encoded_payload, encoded_payload_size)
|
|
120
120
|
decoded_payload = xor_data(encoded_payload, xor_key)
|
|
121
|
-
|
|
122
|
-
|
|
121
|
+
cncs = find_c2(decoded_payload)
|
|
122
|
+
if cncs and list(filter(cncs, None)):
|
|
123
|
+
config["CNCs"] = cncs
|
|
123
124
|
|
|
124
125
|
return config
|
|
125
126
|
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# Copyright (C) 2024 enzok
|
|
2
|
+
# This program is free software: you can redistribute it and/or modify
|
|
3
|
+
# it under the terms of the GNU General Public License as published by
|
|
4
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
5
|
+
# (at your option) any later version.
|
|
6
|
+
#
|
|
7
|
+
# This program is distributed in the hope that it will be useful,
|
|
8
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
9
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
10
|
+
# GNU General Public License for more details.
|
|
11
|
+
#
|
|
12
|
+
# You should have received a copy of the GNU General Public License
|
|
13
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
14
|
+
|
|
15
|
+
import logging
|
|
16
|
+
import struct
|
|
17
|
+
|
|
18
|
+
import pefile
|
|
19
|
+
import yara
|
|
20
|
+
|
|
21
|
+
log = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
DESCRIPTION = "NitroBunnyDownloader configuration parser."
|
|
24
|
+
AUTHOR = "enzok"
|
|
25
|
+
|
|
26
|
+
yara_rule = """
|
|
27
|
+
rule NitroBunnyDownloader
|
|
28
|
+
{
|
|
29
|
+
meta:
|
|
30
|
+
author = "enzok"
|
|
31
|
+
description = "NitroBunnyDownloader Payload"
|
|
32
|
+
cape_type = "NitroBunnyDownloader Payload"
|
|
33
|
+
hash = "960e59200ec0a4b5fb3b44e6da763f5fec4092997975140797d4eec491de411b"
|
|
34
|
+
strings:
|
|
35
|
+
$config1 = {E8 [3] 00 41 B8 ?? ?? 00 00 48 8D 15 [3] 00 48 89 C1 48 89 ?? E8 [3] 00}
|
|
36
|
+
$config2 = {E8 [3] 00 48 8D 15 [3] 00 41 B8 ?? ?? 00 00 48 89 C1 48 89 ?? E8 [3] 00}
|
|
37
|
+
$string1 = "X-Amz-User-Agent:" wide
|
|
38
|
+
$string2 = "Amz-Security-Flag:" wide
|
|
39
|
+
$string3 = "/cart" wide
|
|
40
|
+
$string4 = "Cookie: " wide
|
|
41
|
+
$string5 = "wishlist" wide
|
|
42
|
+
condition:
|
|
43
|
+
uint16(0) == 0x5A4D and 1 of ($config*) and 2 of ($string*)
|
|
44
|
+
}
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
yara_rules = yara.compile(source=yara_rule)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def yara_scan(raw_data):
|
|
51
|
+
try:
|
|
52
|
+
return yara_rules.match(data=raw_data)
|
|
53
|
+
except Exception as e:
|
|
54
|
+
print(e)
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def read_dword(data, off):
|
|
59
|
+
if off + 4 > len(data):
|
|
60
|
+
raise ValueError(f"EOF reading dword at {off}")
|
|
61
|
+
val = struct.unpack_from("<I", data, off)[0]
|
|
62
|
+
return val, off + 4
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def read_qword(data, off):
|
|
66
|
+
"""Read a 64-bit unsigned little-endian value."""
|
|
67
|
+
if off + 8 > len(data):
|
|
68
|
+
raise ValueError(f"EOF reading qword at {off}")
|
|
69
|
+
val = struct.unpack_from("<Q", data, off)[0]
|
|
70
|
+
return val, off + 8
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def read_utf16le_string(data, off, length):
|
|
74
|
+
if off + length > len(data):
|
|
75
|
+
raise ValueError(f"EOF reading string at {off} len={length}")
|
|
76
|
+
raw = data[off:off + length]
|
|
77
|
+
s = raw.decode("utf-16le", errors="replace").rstrip("\x00")
|
|
78
|
+
return s, off + length
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def read_string_list(data, off, count):
|
|
82
|
+
items = []
|
|
83
|
+
for i in range(count):
|
|
84
|
+
length_words, off = read_qword(data, off)
|
|
85
|
+
s, off = read_utf16le_string(data, off, length_words)
|
|
86
|
+
items.append(s)
|
|
87
|
+
return items, off
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def extract_config(filebuf):
|
|
91
|
+
yara_hit = yara_scan(filebuf)
|
|
92
|
+
if not yara_hit:
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
cfg = {}
|
|
96
|
+
config_code_offset = None
|
|
97
|
+
config_size_offset = None
|
|
98
|
+
config_offset = None
|
|
99
|
+
rva_offset = None
|
|
100
|
+
|
|
101
|
+
for hit in yara_hit:
|
|
102
|
+
if hit.rule != "NitroBunnyDownloader":
|
|
103
|
+
continue
|
|
104
|
+
|
|
105
|
+
for item in hit.strings:
|
|
106
|
+
for instance in item.instances:
|
|
107
|
+
if item.identifier == "$config1":
|
|
108
|
+
config_code_offset = instance.offset
|
|
109
|
+
config_size_offset = 7
|
|
110
|
+
config_offset= 14
|
|
111
|
+
rva_offset = 18
|
|
112
|
+
break
|
|
113
|
+
elif item.identifier == "$config2":
|
|
114
|
+
config_code_offset = instance.offset
|
|
115
|
+
config_size_offset = 14
|
|
116
|
+
config_offset= 8
|
|
117
|
+
rva_offset = 12
|
|
118
|
+
break
|
|
119
|
+
|
|
120
|
+
if config_code_offset:
|
|
121
|
+
break
|
|
122
|
+
|
|
123
|
+
if config_code_offset is None:
|
|
124
|
+
return None
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
pe = pefile.PE(data=filebuf, fast_load=True)
|
|
128
|
+
config_length = pe.get_dword_from_offset(config_code_offset + config_size_offset)
|
|
129
|
+
config = pe.get_dword_from_offset(config_code_offset + config_offset)
|
|
130
|
+
rva = pe.get_rva_from_offset(config_code_offset + rva_offset)
|
|
131
|
+
config_rva = rva + config
|
|
132
|
+
data = pe.get_data(config_rva, config_length)
|
|
133
|
+
off = 0
|
|
134
|
+
raw = cfg["raw"] = {}
|
|
135
|
+
port, off = read_dword(data, off)
|
|
136
|
+
num, off = read_dword(data, off)
|
|
137
|
+
cncs, off = read_string_list(data, off, num)
|
|
138
|
+
num, off = read_qword(data, off)
|
|
139
|
+
raw["user_agent"], off = read_utf16le_string(data, off, num)
|
|
140
|
+
num, off = read_dword(data, off)
|
|
141
|
+
raw["http_header_items"], off = read_string_list(data, off, num)
|
|
142
|
+
num, off = read_dword(data, off)
|
|
143
|
+
raw["uri_list"], off = read_string_list(data, off, num)
|
|
144
|
+
raw["unknown_1"], off = read_dword(data, off)
|
|
145
|
+
raw["unknown_2"], off = read_dword(data, off)
|
|
146
|
+
|
|
147
|
+
if cncs:
|
|
148
|
+
cfg["CNCs"] = []
|
|
149
|
+
schema = {80: "http", 443: "https"}.get(port, "tcp")
|
|
150
|
+
for cnc in cncs:
|
|
151
|
+
cnc = f"{schema}://{cnc}"
|
|
152
|
+
if port not in (80, 443):
|
|
153
|
+
cnc += f":{port}"
|
|
154
|
+
|
|
155
|
+
cfg["CNCs"].append(cnc)
|
|
156
|
+
|
|
157
|
+
except Exception as e:
|
|
158
|
+
log.error("Error: %s", e)
|
|
159
|
+
return None
|
|
160
|
+
|
|
161
|
+
return cfg
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
if __name__ == "__main__":
|
|
165
|
+
import sys
|
|
166
|
+
|
|
167
|
+
with open(sys.argv[1], "rb") as f:
|
|
168
|
+
print(extract_config(f.read()))
|
|
@@ -7,6 +7,15 @@ import json
|
|
|
7
7
|
DESCRIPTION = "Rhadamanthys parser"
|
|
8
8
|
AUTHOR = "kevoreilly, YungBinary"
|
|
9
9
|
|
|
10
|
+
CUSTOM_ALPHABETS = [
|
|
11
|
+
b"3Fijkbc|l4NOPQRSTUVWXY567DdewxEqrstuvyz-ABC1fghop2mnGHIJKLMZ089a", # 0.9.3
|
|
12
|
+
b"4NOPQRSTUVWXY567DdeEqrstuvwxyz-ABC1fghop23Fijkbc|lmnGHIJKLMZ089a", # 0.9.2
|
|
13
|
+
b"ABC1fghijklmnop234NOPQRSTUVWXY567DEFGHIJKLMZ089abcdeqrstuvwxyz-|", # 0.9.X
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
CHACHA_KEY = b"\x52\xAB\xDF\x06\xB6\xB1\x3A\xC0\xDA\x2D\x22\xDC\x6C\xD2\xBE\x6C\x20\x17\x69\xE0\x12\xB5\xE6\xEC\x0E\xAB\x4C\x14\x73\x4A\xED\x51"
|
|
17
|
+
CHACHA_NONCE = b"\x5F\x14\xD7\x9C\xFC\xFC\x43\x9E\xC3\x40\x6B\xBA"
|
|
18
|
+
|
|
10
19
|
|
|
11
20
|
def mask32(x):
|
|
12
21
|
return x & 0xFFFFFFFF
|
|
@@ -150,6 +159,8 @@ def lzo_noheader_decompress(data: bytes, decompressed_size: int):
|
|
|
150
159
|
ctrl = data[src]
|
|
151
160
|
src += 1
|
|
152
161
|
|
|
162
|
+
# Special short match
|
|
163
|
+
# Copies exactly 3 bytes from dst starting match_len + 1 bytes back.
|
|
153
164
|
if ctrl == 0x20:
|
|
154
165
|
match_len = data[src]
|
|
155
166
|
src += 1
|
|
@@ -159,21 +170,28 @@ def lzo_noheader_decompress(data: bytes, decompressed_size: int):
|
|
|
159
170
|
dst.extend(dst[start:end])
|
|
160
171
|
|
|
161
172
|
elif ctrl >= 0xE0 or ctrl == 0x40:
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
elif ctrl == 0x40:
|
|
166
|
-
copy_len = x
|
|
167
|
-
start = data[src + 1]
|
|
168
|
-
if ctrl == 0x40:
|
|
169
|
-
start = data[src]
|
|
173
|
+
# Compute base copy length from the upper bits of ctrl
|
|
174
|
+
base_len = ((ctrl >> 5) - 1) + 3
|
|
175
|
+
|
|
170
176
|
if ctrl >= 0xE0:
|
|
177
|
+
# Long copy: extra length byte follows
|
|
178
|
+
copy_len = base_len + data[src]
|
|
179
|
+
# Offset is byte after
|
|
180
|
+
start = data[src + 1]
|
|
171
181
|
src += 2
|
|
172
182
|
elif ctrl == 0x40:
|
|
183
|
+
# Short copy: offset byte after control code
|
|
184
|
+
copy_len = base_len
|
|
185
|
+
start = data[src]
|
|
173
186
|
src += 1
|
|
187
|
+
|
|
188
|
+
# Calculate offset in output buffer
|
|
174
189
|
offset = len(dst) - start - 1
|
|
190
|
+
|
|
175
191
|
#print(f"Control code: {hex(ctrl)}, Offset backtrack length: {hex(start)}, Current offset: {hex(len(dst))}, New offset: {hex(len(dst) - start)}, Length to copy: {hex(copy_len)}")
|
|
176
|
-
|
|
192
|
+
|
|
193
|
+
# Copy from previously decompressed data
|
|
194
|
+
dst.extend(dst[offset:offset + copy_len])
|
|
177
195
|
|
|
178
196
|
else:
|
|
179
197
|
# Literal run
|
|
@@ -231,62 +249,71 @@ def parse_compression_header(config: bytes):
|
|
|
231
249
|
}
|
|
232
250
|
|
|
233
251
|
|
|
252
|
+
def handle_encrypted_string(encrypted_string: str) -> list:
|
|
253
|
+
"""
|
|
254
|
+
Args:
|
|
255
|
+
encrypted_string: a str representing
|
|
256
|
+
Returns:
|
|
257
|
+
Command and Control server list, may be empty
|
|
258
|
+
"""
|
|
259
|
+
|
|
260
|
+
for alphabet in CUSTOM_ALPHABETS:
|
|
261
|
+
try:
|
|
262
|
+
custom_b64_decoded = custom_b64decode(encrypted_string, alphabet)
|
|
263
|
+
xor_key = chacha20_xor(custom_b64_decoded, CHACHA_KEY, CHACHA_NONCE)
|
|
264
|
+
# Decrypted, may still be the compressed malware configuration
|
|
265
|
+
config = decrypt_config(xor_key)
|
|
266
|
+
|
|
267
|
+
# First byte should be 0xFF
|
|
268
|
+
if config[0] != 0xFF:
|
|
269
|
+
continue
|
|
270
|
+
|
|
271
|
+
# Attempt to extract C2 url, only works in version prior to 0.9.2
|
|
272
|
+
c2_url = extract_c2_url(config)
|
|
273
|
+
if c2_url:
|
|
274
|
+
return [c2_url]
|
|
275
|
+
|
|
276
|
+
# Parse header
|
|
277
|
+
parsed = parse_compression_header(config)
|
|
278
|
+
if not parsed:
|
|
279
|
+
continue
|
|
280
|
+
|
|
281
|
+
# Decompress LZO-like compression
|
|
282
|
+
decompressed = lzo_noheader_decompress(parsed['compressed_data'], parsed['decompressed_size'])
|
|
283
|
+
pattern = re.compile(b'.' + bytes([decompressed[1]]))
|
|
284
|
+
|
|
285
|
+
cncs = [f"https://{chunk.decode()}" for chunk in pattern.split(decompressed) if chunk]
|
|
286
|
+
return cncs
|
|
287
|
+
except Exception:
|
|
288
|
+
continue
|
|
289
|
+
|
|
290
|
+
return []
|
|
291
|
+
|
|
292
|
+
|
|
234
293
|
def extract_config(data):
|
|
294
|
+
"""
|
|
295
|
+
Extract Rhadamanthys malware configuration.
|
|
296
|
+
"""
|
|
235
297
|
config_dict = {}
|
|
298
|
+
# Extract very old variant
|
|
236
299
|
magic = struct.unpack("I", data[:4])[0]
|
|
237
300
|
if magic == 0x59485221:
|
|
238
301
|
config_dict["CNCs"] = [data[24:].split(b"\0", 1)[0].decode()]
|
|
239
302
|
return config_dict
|
|
240
|
-
else:
|
|
241
|
-
key = b"\x52\xAB\xDF\x06\xB6\xB1\x3A\xC0\xDA\x2D\x22\xDC\x6C\xD2\xBE\x6C\x20\x17\x69\xE0\x12\xB5\xE6\xEC\x0E\xAB\x4C\x14\x73\x4A\xED\x51"
|
|
242
|
-
nonce = b"\x5F\x14\xD7\x9C\xFC\xFC\x43\x9E\xC3\x40\x6B\xBA"
|
|
243
|
-
|
|
244
|
-
custom_alphabets = [
|
|
245
|
-
b"ABC1fghijklmnop234NOPQRSTUVWXY567DEFGHIJKLMZ089abcdeqrstuvwxyz-|",
|
|
246
|
-
b"4NOPQRSTUVWXY567DdeEqrstuvwxyz-ABC1fghop23Fijkbc|lmnGHIJKLMZ089a"
|
|
247
|
-
]
|
|
248
|
-
|
|
249
|
-
# Extract base64 strings
|
|
250
|
-
extracted_strings = extract_base64_strings(data, 140, 256)
|
|
251
|
-
if not extracted_strings:
|
|
252
|
-
return config_dict
|
|
253
|
-
|
|
254
|
-
pattern = re.compile(b'.\x80')
|
|
255
|
-
for string in extracted_strings:
|
|
256
|
-
try:
|
|
257
|
-
custom_b64_decoded = custom_b64decode(string, custom_alphabets[0])
|
|
258
|
-
|
|
259
|
-
xor_key = chacha20_xor(custom_b64_decoded, key, nonce)
|
|
260
|
-
|
|
261
|
-
# Decrypted, but may still be the compressed malware configuration
|
|
262
|
-
config = decrypt_config(xor_key)
|
|
263
|
-
# Attempt to extract C2 url, only works in version prior to 0.9.2
|
|
264
|
-
c2_url = extract_c2_url(config)
|
|
265
|
-
if c2_url:
|
|
266
|
-
config_dict = {"CNCs": [c2_url]}
|
|
267
|
-
return config_dict
|
|
268
|
-
else:
|
|
269
|
-
# Handle new variants that compress the Command and Control server(s)
|
|
270
|
-
custom_b64_decoded = custom_b64decode(string, custom_alphabets[1])
|
|
271
|
-
xor_key = chacha20_xor(custom_b64_decoded, key, nonce)
|
|
272
|
-
config = decrypt_config(xor_key)
|
|
273
|
-
|
|
274
|
-
parsed = parse_compression_header(config)
|
|
275
|
-
if not parsed:
|
|
276
|
-
return config_dict
|
|
277
|
-
|
|
278
|
-
decompressed = lzo_noheader_decompress(parsed['compressed_data'], parsed['decompressed_size'])
|
|
279
|
-
|
|
280
|
-
cncs = [f"https://{chunk.decode()}" for chunk in pattern.split(decompressed) if chunk]
|
|
281
|
-
if cncs:
|
|
282
|
-
config_dict = {"CNCs": cncs}
|
|
283
|
-
return config_dict
|
|
284
|
-
|
|
285
|
-
except Exception:
|
|
286
|
-
continue
|
|
287
303
|
|
|
304
|
+
# New variants, extract base64 strings
|
|
305
|
+
extracted_strings = extract_base64_strings(data, 100, 256)
|
|
306
|
+
if not extracted_strings:
|
|
288
307
|
return config_dict
|
|
289
308
|
|
|
309
|
+
# Handle each encrypted string
|
|
310
|
+
for string in extracted_strings:
|
|
311
|
+
cncs = handle_encrypted_string(string)
|
|
312
|
+
if cncs:
|
|
313
|
+
return {"CNCs": cncs}
|
|
314
|
+
|
|
315
|
+
return config_dict
|
|
316
|
+
|
|
290
317
|
|
|
291
318
|
if __name__ == "__main__":
|
|
292
319
|
import sys
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/community/CobaltStrikeBeacon.py
RENAMED
|
File without changes
|
{cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/community/CobaltStrikeStager.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cape_parsers-0.1.53 → cape_parsers-0.1.55}/cape_parsers/CAPE/community/PhemedroneStealer.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|