CAPE-parsers 0.1.54__tar.gz → 0.1.56__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.54 → cape_parsers-0.1.56}/PKG-INFO +1 -1
- cape_parsers-0.1.56/cape_parsers/CAPE/community/Amatera.py +186 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/community/KoiLoader.py +3 -2
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/community/Lumma.py +5 -10
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/core/NitroBunnyDownloader.py +24 -7
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/core/Rhadamanthys.py +65 -61
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/pyproject.toml +1 -1
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/LICENSE +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/README.md +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/__init__.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/community/AgentTesla.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/community/Amadey.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/community/Arkei.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/community/AsyncRAT.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/community/AuroraStealer.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/community/Carbanak.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/community/CobaltStrikeBeacon.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/community/CobaltStrikeStager.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/community/DCRat.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/community/Fareit.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/community/LokiBot.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/community/MonsterV2.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/community/MyKings.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/community/NanoCore.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/community/Nighthawk.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/community/Njrat.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/community/PhemedroneStealer.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/community/QuasarRAT.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/community/README.md +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/community/Snake.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/community/SparkRAT.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/community/Stealc.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/community/VenomRAT.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/community/WinosStager.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/community/XWorm.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/community/XenoRAT.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/community/__init__.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/core/AdaptixBeacon.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/core/AuraStealer.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/core/Azorult.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/core/BitPaymer.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/core/BlackDropper.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/core/Blister.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/core/BruteRatel.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/core/BumbleBee.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/core/DarkGate.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/core/DoppelPaymer.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/core/DridexLoader.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/core/Formbook.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/core/GuLoader.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/core/IcedID.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/core/IcedIDLoader.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/core/Latrodectus.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/core/Oyster.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/core/PikaBot.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/core/PlugX.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/core/QakBot.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/core/Quickbind.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/core/README.md +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/core/RedLine.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/core/Remcos.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/core/SmokeLoader.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/core/Socks5Systemz.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/core/SquirrelWaffle.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/core/Strrat.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/core/WarzoneRAT.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/core/Zloader.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/core/__init__.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/core/test_cape.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/RATDecoders/README.md +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/RATDecoders/__init__.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/RATDecoders/test_rats.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/__init__.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/deprecated/BackOffLoader.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/deprecated/BackOffPOS.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/deprecated/BlackNix.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/deprecated/BuerLoader.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/deprecated/ChChes.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/deprecated/Emotet.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/deprecated/Enfal.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/deprecated/EvilGrab.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/deprecated/Greame.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/deprecated/Hancitor.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/deprecated/HttpBrowser.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/deprecated/JavaDropper.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/deprecated/Nymaim.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/deprecated/Pandora.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/deprecated/PoisonIvy.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/deprecated/PredatorPain.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/deprecated/Punisher.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/deprecated/RCSession.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/deprecated/REvil.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/deprecated/RedLeaf.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/deprecated/Retefe.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/deprecated/Rozena.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/deprecated/SmallNet.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/deprecated/TSCookie.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/deprecated/TrickBot.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/deprecated/UrsnifV3.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/deprecated/_ShadowTech.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/deprecated/_VirusRat.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/deprecated/_jRat.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/deprecated/unrecom.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/deprecated/xRAT.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/malduck/LICENSE +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/malduck/README.md +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/malduck/__init__.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/malduck/test_malduck.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/mwcp/README.md +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/mwcp/__init__.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/mwcp/test_mwcp.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/utils/__init__.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/utils/aplib.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/utils/blzpack.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/utils/blzpack_lib.so +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/utils/dotnet_utils.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/utils/lznt1.py +0 -0
- {cape_parsers-0.1.54 → cape_parsers-0.1.56}/cape_parsers/utils/strings.py +0 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import json
|
|
3
|
+
import pefile
|
|
4
|
+
import yara
|
|
5
|
+
import struct
|
|
6
|
+
import re
|
|
7
|
+
import ipaddress
|
|
8
|
+
from contextlib import suppress
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
DESCRIPTION = "Amatera Stealer parser"
|
|
12
|
+
AUTHOR = "YungBinary"
|
|
13
|
+
|
|
14
|
+
RULE_SOURCE = """
|
|
15
|
+
rule AmateraDecrypt
|
|
16
|
+
{
|
|
17
|
+
meta:
|
|
18
|
+
author = "YungBinary"
|
|
19
|
+
description = "Find Amatera XOR key"
|
|
20
|
+
strings:
|
|
21
|
+
$decrypt = {
|
|
22
|
+
A1 ?? ?? ?? ?? // mov eax, dword ptr ds:szXorKey ; "852149723"
|
|
23
|
+
89 45 ?? // mov dword ptr [ebp+xor_key], eax
|
|
24
|
+
8B 0D ?? ?? ?? ?? // mov ecx, dword ptr ds:szXorKey+4 ; "49723"
|
|
25
|
+
89 4D ?? // mov dword ptr [ebp+xor_key+4], ecx
|
|
26
|
+
66 8B 15 ?? ?? ?? ?? // mov dx, word ptr ds:szXorKey+8 ; "3"
|
|
27
|
+
66 89 55 ?? // mov word ptr [ebp+xor_key+8], dx
|
|
28
|
+
8D 45 ?? // lea eax, [ebp+xor_key]
|
|
29
|
+
50 // push eax
|
|
30
|
+
E8 // call
|
|
31
|
+
}
|
|
32
|
+
condition:
|
|
33
|
+
uint16(0) == 0x5A4D and $decrypt
|
|
34
|
+
}
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
RULE_SOURCE_AES_KEY = """
|
|
39
|
+
rule AmateraAESKey
|
|
40
|
+
{
|
|
41
|
+
meta:
|
|
42
|
+
author = "YungBinary"
|
|
43
|
+
description = "Find Amatera AES key"
|
|
44
|
+
strings:
|
|
45
|
+
$aes_key_on_stack = {
|
|
46
|
+
83 EC 2C // sub esp, 2Ch
|
|
47
|
+
C6 45 D4 ?? // mov byte ptr [ebp-2Ch], ??
|
|
48
|
+
C6 45 D5 ?? // mov byte ptr [ebp-2Bh], ??
|
|
49
|
+
C6 45 D6 ?? // mov byte ptr [ebp-2Ah], ??
|
|
50
|
+
C6 45 D7 ?? // mov byte ptr [ebp-29h], ??
|
|
51
|
+
C6 45 D8 ?? // mov byte ptr [ebp-28h], ??
|
|
52
|
+
C6 45 D9 ?? // mov byte ptr [ebp-27h], ??
|
|
53
|
+
C6 45 DA ?? // mov byte ptr [ebp-26h], ??
|
|
54
|
+
C6 45 DB ?? // mov byte ptr [ebp-25h], ??
|
|
55
|
+
C6 45 DC ?? // mov byte ptr [ebp-24h], ??
|
|
56
|
+
C6 45 DD ?? // mov byte ptr [ebp-23h], ??
|
|
57
|
+
C6 45 DE ?? // mov byte ptr [ebp-22h], ??
|
|
58
|
+
C6 45 DF ?? // mov byte ptr [ebp-21h], ??
|
|
59
|
+
C6 45 E0 ?? // mov byte ptr [ebp-20h], ??
|
|
60
|
+
C6 45 E1 ?? // mov byte ptr [ebp-1Fh], ??
|
|
61
|
+
C6 45 E2 ?? // mov byte ptr [ebp-1Eh], ??
|
|
62
|
+
C6 45 E3 ?? // mov byte ptr [ebp-1Dh], ??
|
|
63
|
+
C6 45 E4 ?? // mov byte ptr [ebp-1Ch], ??
|
|
64
|
+
C6 45 E5 ?? // mov byte ptr [ebp-1Bh], ??
|
|
65
|
+
C6 45 E6 ?? // mov byte ptr [ebp-1Ah], ??
|
|
66
|
+
C6 45 E7 ?? // mov byte ptr [ebp-19h], ??
|
|
67
|
+
C6 45 E8 ?? // mov byte ptr [ebp-18h], ??
|
|
68
|
+
C6 45 E9 ?? // mov byte ptr [ebp-17h], ??
|
|
69
|
+
C6 45 EA ?? // mov byte ptr [ebp-16h], ??
|
|
70
|
+
C6 45 EB ?? // mov byte ptr [ebp-15h], ??
|
|
71
|
+
C6 45 EC ?? // mov byte ptr [ebp-14h], ??
|
|
72
|
+
C6 45 ED ?? // mov byte ptr [ebp-13h], ??
|
|
73
|
+
C6 45 EE ?? // mov byte ptr [ebp-12h], ??
|
|
74
|
+
C6 45 EF ?? // mov byte ptr [ebp-11h], ??
|
|
75
|
+
C6 45 F0 ?? // mov byte ptr [ebp-10h], ??
|
|
76
|
+
C6 45 F1 ?? // mov byte ptr [ebp-0Fh], ??
|
|
77
|
+
C6 45 F2 ?? // mov byte ptr [ebp-0Eh], ??
|
|
78
|
+
C6 45 F3 ?? // mov byte ptr [ebp-0Dh], ??
|
|
79
|
+
C7 45 F4 10 00 00 00 // mov dword ptr [ebp-0Ch], 10h
|
|
80
|
+
}
|
|
81
|
+
condition:
|
|
82
|
+
uint16(0) == 0x5A4D and $aes_key_on_stack
|
|
83
|
+
}
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
DOMAIN_REGEX = r'^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$'
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def yara_scan(raw_data: bytes, rule_source: str):
|
|
90
|
+
yara_rules = yara.compile(source=rule_source)
|
|
91
|
+
matches = yara_rules.match(data=raw_data)
|
|
92
|
+
|
|
93
|
+
for match in matches:
|
|
94
|
+
for block in match.strings:
|
|
95
|
+
for instance in block.instances:
|
|
96
|
+
return instance.offset
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def extract_base64_strings(data: bytes, minchars: int, maxchars: int):
|
|
100
|
+
"""
|
|
101
|
+
Generator that returns ASCII formatted base64 strings
|
|
102
|
+
"""
|
|
103
|
+
apat = b"([A-Za-z0-9+/=]{" + str(minchars).encode() + b"," + str(maxchars).encode() + b"})\x00"
|
|
104
|
+
for s in re.findall(apat, data):
|
|
105
|
+
yield s.decode()
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def xor_data(data, key):
|
|
109
|
+
decoded = bytearray()
|
|
110
|
+
for i in range(len(data)):
|
|
111
|
+
decoded.append(key[i % len(key)] ^ data[i])
|
|
112
|
+
return decoded
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def is_public_ip(ip):
|
|
116
|
+
try:
|
|
117
|
+
# This will raise a ValueError if the IP format is incorrect
|
|
118
|
+
ip_obj = ipaddress.ip_address(ip.decode())
|
|
119
|
+
if ip_obj.is_private:
|
|
120
|
+
return False
|
|
121
|
+
return True
|
|
122
|
+
except Exception:
|
|
123
|
+
return False
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def is_valid_domain(data):
|
|
127
|
+
try:
|
|
128
|
+
if re.fullmatch(DOMAIN_REGEX, data.decode()):
|
|
129
|
+
return True
|
|
130
|
+
return False
|
|
131
|
+
except Exception:
|
|
132
|
+
return False
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def extract_config(data):
|
|
136
|
+
"""
|
|
137
|
+
Extract Amatera malware configuration.
|
|
138
|
+
"""
|
|
139
|
+
config_dict = {}
|
|
140
|
+
|
|
141
|
+
with suppress(Exception):
|
|
142
|
+
pe = pefile.PE(data=data)
|
|
143
|
+
image_base = pe.OPTIONAL_HEADER.ImageBase
|
|
144
|
+
|
|
145
|
+
# Identify XOR key decryption routine and extract key
|
|
146
|
+
offset = yara_scan(data, RULE_SOURCE)
|
|
147
|
+
if not offset:
|
|
148
|
+
return config_dict
|
|
149
|
+
key_str_va = struct.unpack('i', data[offset + 1: offset + 5])[0]
|
|
150
|
+
key_str = pe.get_string_at_rva(key_str_va - image_base, max_length=20) + b'\x00'
|
|
151
|
+
|
|
152
|
+
# Extract AES 256 key
|
|
153
|
+
aes_key_offset = yara_scan(data, RULE_SOURCE_AES_KEY)
|
|
154
|
+
aes_key = bytearray()
|
|
155
|
+
if aes_key_offset:
|
|
156
|
+
aes_block = data[aes_key_offset : aes_key_offset + 131]
|
|
157
|
+
for i in range(0, len(aes_block) - 4, 4):
|
|
158
|
+
aes_key.append(aes_block[i+6])
|
|
159
|
+
|
|
160
|
+
# Handle each base64 string -> decode -> decrypt with XOR key
|
|
161
|
+
for b64_str in extract_base64_strings(data, 8, 20):
|
|
162
|
+
try:
|
|
163
|
+
decoded = base64.b64decode(b64_str, validate=True)
|
|
164
|
+
decrypted = xor_data(decoded, key_str)
|
|
165
|
+
if not is_public_ip(decrypted) and not is_valid_domain(decrypted):
|
|
166
|
+
continue
|
|
167
|
+
|
|
168
|
+
config_dict["CNCs"] = [f"https://{decrypted.decode()}"]
|
|
169
|
+
|
|
170
|
+
if aes_key:
|
|
171
|
+
config_dict["cryptokey"] = aes_key.hex()
|
|
172
|
+
config_dict["cryptokey_type"] = "AES"
|
|
173
|
+
|
|
174
|
+
return config_dict
|
|
175
|
+
except Exception:
|
|
176
|
+
continue
|
|
177
|
+
|
|
178
|
+
return config_dict
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
if __name__ == "__main__":
|
|
182
|
+
import sys
|
|
183
|
+
|
|
184
|
+
with open(sys.argv[1], "rb") as f:
|
|
185
|
+
config_json = json.dumps(extract_config(f.read()), indent=4)
|
|
186
|
+
print(config_json)
|
|
@@ -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:
|
|
123
|
+
config["CNCs"] = cncs
|
|
123
124
|
|
|
124
125
|
return config
|
|
125
126
|
|
|
@@ -5,7 +5,7 @@ import struct
|
|
|
5
5
|
from contextlib import suppress
|
|
6
6
|
import pefile
|
|
7
7
|
import yara
|
|
8
|
-
|
|
8
|
+
|
|
9
9
|
|
|
10
10
|
RULE_SOURCE_BUILD_ID = """rule LummaBuildId
|
|
11
11
|
{
|
|
@@ -142,7 +142,7 @@ def contains_non_printable(byte_array):
|
|
|
142
142
|
return True
|
|
143
143
|
return False
|
|
144
144
|
|
|
145
|
-
|
|
145
|
+
|
|
146
146
|
def mask32(x):
|
|
147
147
|
return x & 0xFFFFFFFF
|
|
148
148
|
|
|
@@ -242,7 +242,7 @@ def chacha20_xor(message, key, nonce, counter):
|
|
|
242
242
|
xor_key.append(message[i] ^ key_stream[i])
|
|
243
243
|
|
|
244
244
|
return xor_key
|
|
245
|
-
|
|
245
|
+
|
|
246
246
|
|
|
247
247
|
def extract_c2_domain(data):
|
|
248
248
|
pattern = rb"([\w-]+\.[\w]+)\x00"
|
|
@@ -315,9 +315,7 @@ def extract_config(data):
|
|
|
315
315
|
counter = 2
|
|
316
316
|
for i in range(12):
|
|
317
317
|
encrypted_string = data[encrypted_strings_offset : encrypted_strings_offset + 40]
|
|
318
|
-
|
|
319
|
-
chacha20_cipher.seek(counter)
|
|
320
|
-
decoded_c2 = chacha20_cipher.decrypt(encrypted_string).split(b"\x00", 1)[0]
|
|
318
|
+
decoded_c2 = chacha20_xor(encrypted_string, key, nonce, counter).split(b"\x00", 1)[0]
|
|
321
319
|
if contains_non_printable(decoded_c2):
|
|
322
320
|
break
|
|
323
321
|
config.setdefault("CNCs", []).append("https://" + decoded_c2.decode())
|
|
@@ -350,10 +348,7 @@ def extract_config(data):
|
|
|
350
348
|
c2_encrypted = data[c2_dword_offset : c2_dword_offset + 0x80]
|
|
351
349
|
counters = [0, 2, 4, 6, 8, 10, 12, 14, 16]
|
|
352
350
|
for counter in counters:
|
|
353
|
-
|
|
354
|
-
chacha20_cipher = ChaCha20.new(key=key, nonce=nonce)
|
|
355
|
-
chacha20_cipher.seek(counter)
|
|
356
|
-
decrypted = chacha20_cipher.decrypt(c2_encrypted).split(b"\x00", 1)[0]
|
|
351
|
+
decrypted = chacha20_xor(c2_encrypted, key, nonce, counter)
|
|
357
352
|
c2 = extract_c2_domain(decrypted)
|
|
358
353
|
if c2 is not None and len(c2) > 10:
|
|
359
354
|
config["CNCs"].append("https://" + c2.decode())
|
|
@@ -32,14 +32,15 @@ rule NitroBunnyDownloader
|
|
|
32
32
|
cape_type = "NitroBunnyDownloader Payload"
|
|
33
33
|
hash = "960e59200ec0a4b5fb3b44e6da763f5fec4092997975140797d4eec491de411b"
|
|
34
34
|
strings:
|
|
35
|
-
$
|
|
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}
|
|
36
37
|
$string1 = "X-Amz-User-Agent:" wide
|
|
37
38
|
$string2 = "Amz-Security-Flag:" wide
|
|
38
39
|
$string3 = "/cart" wide
|
|
39
40
|
$string4 = "Cookie: " wide
|
|
40
41
|
$string5 = "wishlist" wide
|
|
41
42
|
condition:
|
|
42
|
-
uint16(0) == 0x5A4D and $config and 2 of ($string*)
|
|
43
|
+
uint16(0) == 0x5A4D and 1 of ($config*) and 2 of ($string*)
|
|
43
44
|
}
|
|
44
45
|
"""
|
|
45
46
|
|
|
@@ -93,25 +94,41 @@ def extract_config(filebuf):
|
|
|
93
94
|
|
|
94
95
|
cfg = {}
|
|
95
96
|
config_code_offset = None
|
|
97
|
+
config_size_offset = None
|
|
98
|
+
config_offset = None
|
|
99
|
+
rva_offset = None
|
|
100
|
+
|
|
96
101
|
for hit in yara_hit:
|
|
97
102
|
if hit.rule != "NitroBunnyDownloader":
|
|
98
103
|
continue
|
|
99
104
|
|
|
100
105
|
for item in hit.strings:
|
|
101
106
|
for instance in item.instances:
|
|
102
|
-
if "$
|
|
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":
|
|
103
114
|
config_code_offset = instance.offset
|
|
115
|
+
config_size_offset = 14
|
|
116
|
+
config_offset= 8
|
|
117
|
+
rva_offset = 12
|
|
104
118
|
break
|
|
105
119
|
|
|
120
|
+
if config_code_offset:
|
|
121
|
+
break
|
|
122
|
+
|
|
106
123
|
if config_code_offset is None:
|
|
107
124
|
return None
|
|
108
125
|
|
|
109
126
|
try:
|
|
110
127
|
pe = pefile.PE(data=filebuf, fast_load=True)
|
|
111
|
-
config_length = pe.get_dword_from_offset(config_code_offset +
|
|
112
|
-
|
|
113
|
-
rva = pe.get_rva_from_offset(config_code_offset +
|
|
114
|
-
config_rva = rva +
|
|
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
|
|
115
132
|
data = pe.get_data(config_rva, config_length)
|
|
116
133
|
off = 0
|
|
117
134
|
raw = cfg["raw"] = {}
|
|
@@ -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
|
|
@@ -240,76 +249,71 @@ def parse_compression_header(config: bytes):
|
|
|
240
249
|
}
|
|
241
250
|
|
|
242
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
|
+
|
|
243
293
|
def extract_config(data):
|
|
294
|
+
"""
|
|
295
|
+
Extract Rhadamanthys malware configuration.
|
|
296
|
+
"""
|
|
244
297
|
config_dict = {}
|
|
298
|
+
# Extract very old variant
|
|
245
299
|
magic = struct.unpack("I", data[:4])[0]
|
|
246
300
|
if magic == 0x59485221:
|
|
247
301
|
config_dict["CNCs"] = [data[24:].split(b"\0", 1)[0].decode()]
|
|
248
302
|
return config_dict
|
|
249
|
-
else:
|
|
250
|
-
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"
|
|
251
|
-
nonce = b"\x5F\x14\xD7\x9C\xFC\xFC\x43\x9E\xC3\x40\x6B\xBA"
|
|
252
|
-
|
|
253
|
-
custom_alphabets = [
|
|
254
|
-
b"ABC1fghijklmnop234NOPQRSTUVWXY567DEFGHIJKLMZ089abcdeqrstuvwxyz-|",
|
|
255
|
-
b"4NOPQRSTUVWXY567DdeEqrstuvwxyz-ABC1fghop23Fijkbc|lmnGHIJKLMZ089a", # 0.9.2
|
|
256
|
-
b"3Fijkbc|l4NOPQRSTUVWXY567DdewxEqrstuvyz-ABC1fghop2mnGHIJKLMZ089a", # 0.9.3
|
|
257
|
-
]
|
|
258
|
-
|
|
259
|
-
# Extract base64 strings
|
|
260
|
-
extracted_strings = extract_base64_strings(data, 100, 256)
|
|
261
|
-
if not extracted_strings:
|
|
262
|
-
return config_dict
|
|
263
|
-
|
|
264
|
-
pattern = re.compile(b'.\x80')
|
|
265
|
-
for string in extracted_strings:
|
|
266
|
-
try:
|
|
267
|
-
custom_b64_decoded = custom_b64decode(string, custom_alphabets[0])
|
|
268
|
-
|
|
269
|
-
xor_key = chacha20_xor(custom_b64_decoded, key, nonce)
|
|
270
|
-
|
|
271
|
-
# Decrypted, but may still be the compressed malware configuration
|
|
272
|
-
config = decrypt_config(xor_key)
|
|
273
|
-
# Attempt to extract C2 url, only works in version prior to 0.9.2
|
|
274
|
-
c2_url = extract_c2_url(config)
|
|
275
|
-
if c2_url:
|
|
276
|
-
config_dict = {"CNCs": [c2_url]}
|
|
277
|
-
return config_dict
|
|
278
|
-
else:
|
|
279
|
-
# Handle new variants that compress the Command and Control server(s)
|
|
280
|
-
custom_b64_decoded = custom_b64decode(string, custom_alphabets[2])
|
|
281
|
-
xor_key = chacha20_xor(custom_b64_decoded, key, nonce)
|
|
282
|
-
config = decrypt_config(xor_key)
|
|
283
|
-
|
|
284
|
-
parsed = parse_compression_header(config)
|
|
285
|
-
if not parsed:
|
|
286
|
-
return config_dict
|
|
287
|
-
|
|
288
|
-
decompressed = lzo_noheader_decompress(parsed['compressed_data'], parsed['decompressed_size'])
|
|
289
|
-
|
|
290
|
-
# Try old alphabet for 0.9.2
|
|
291
|
-
if not decompressed:
|
|
292
|
-
custom_b64_decoded = custom_b64decode(string, custom_alphabets[1])
|
|
293
|
-
xor_key = chacha20_xor(custom_b64_decoded, key, nonce)
|
|
294
|
-
config = decrypt_config(xor_key)
|
|
295
|
-
|
|
296
|
-
parsed = parse_compression_header(config)
|
|
297
|
-
if not parsed:
|
|
298
|
-
return config_dict
|
|
299
|
-
|
|
300
|
-
decompressed = lzo_noheader_decompress(parsed['compressed_data'], parsed['decompressed_size'])
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
cncs = [f"https://{chunk.decode()}" for chunk in pattern.split(decompressed) if chunk]
|
|
304
|
-
if cncs:
|
|
305
|
-
config_dict = {"CNCs": cncs}
|
|
306
|
-
return config_dict
|
|
307
|
-
|
|
308
|
-
except Exception:
|
|
309
|
-
continue
|
|
310
303
|
|
|
304
|
+
# New variants, extract base64 strings
|
|
305
|
+
extracted_strings = extract_base64_strings(data, 100, 256)
|
|
306
|
+
if not extracted_strings:
|
|
311
307
|
return config_dict
|
|
312
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
|
+
|
|
313
317
|
|
|
314
318
|
if __name__ == "__main__":
|
|
315
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.54 → cape_parsers-0.1.56}/cape_parsers/CAPE/community/CobaltStrikeBeacon.py
RENAMED
|
File without changes
|
{cape_parsers-0.1.54 → cape_parsers-0.1.56}/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
|
{cape_parsers-0.1.54 → cape_parsers-0.1.56}/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
|