CAPE-parsers 0.1.55__py3-none-any.whl → 0.1.56__py3-none-any.whl
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/CAPE/community/Amatera.py +186 -0
- cape_parsers/CAPE/community/KoiLoader.py +1 -1
- cape_parsers/CAPE/core/NitroBunnyDownloader.py +1 -1
- {cape_parsers-0.1.55.dist-info → cape_parsers-0.1.56.dist-info}/METADATA +1 -1
- {cape_parsers-0.1.55.dist-info → cape_parsers-0.1.56.dist-info}/RECORD +7 -6
- {cape_parsers-0.1.55.dist-info → cape_parsers-0.1.56.dist-info}/WHEEL +0 -0
- {cape_parsers-0.1.55.dist-info → cape_parsers-0.1.56.dist-info}/licenses/LICENSE +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)
|
|
@@ -119,7 +119,7 @@ def extract_config(data):
|
|
|
119
119
|
encoded_payload = remove_nulls(encoded_payload, encoded_payload_size)
|
|
120
120
|
decoded_payload = xor_data(encoded_payload, xor_key)
|
|
121
121
|
cncs = find_c2(decoded_payload)
|
|
122
|
-
if cncs
|
|
122
|
+
if cncs:
|
|
123
123
|
config["CNCs"] = cncs
|
|
124
124
|
|
|
125
125
|
return config
|
|
@@ -33,7 +33,7 @@ rule NitroBunnyDownloader
|
|
|
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
|
+
$config2 = {E8 [3] 00 48 8D 15 [3] 00 41 B8 ?? ?? 00 00 48 89 C1 48 89 ?? E8 [3] 00}
|
|
37
37
|
$string1 = "X-Amz-User-Agent:" wide
|
|
38
38
|
$string2 = "Amz-Security-Flag:" wide
|
|
39
39
|
$string3 = "/cart" wide
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
cape_parsers/CAPE/__init__.py,sha256=JcY8WPKzUFYgexwV1eyKIuT1JyNZzMJjBynlPSzxY_I,7
|
|
2
2
|
cape_parsers/CAPE/community/AgentTesla.py,sha256=ln5MqFXkTb7WrlDrUHNTnMWBYRHDSqyK4VHeq0ZldtA,4047
|
|
3
3
|
cape_parsers/CAPE/community/Amadey.py,sha256=IUyt909q9IDQPPip6UW9uD16rJMD_gvkwvNZ8NHTW-k,5577
|
|
4
|
+
cape_parsers/CAPE/community/Amatera.py,sha256=8s94SW58r04bs2KuIdJJ5-dm4WfoOrwINEqdSuFJKHk,7096
|
|
4
5
|
cape_parsers/CAPE/community/Arkei.py,sha256=k36qHxdo5yPa9V1cg7EImSWP06kMog0rBda4KXqLKCY,3783
|
|
5
6
|
cape_parsers/CAPE/community/AsyncRAT.py,sha256=0-FRT3d2x63KQ_cs1xmKFj7x0JRf7ID6QDc_DvBa0PM,1003
|
|
6
7
|
cape_parsers/CAPE/community/AuroraStealer.py,sha256=LRu2QFBYkGhRGDJBw3GlcKub4E0_TBWmjdR2PnobDZM,2643
|
|
@@ -9,7 +10,7 @@ cape_parsers/CAPE/community/CobaltStrikeBeacon.py,sha256=U4Q0ObCrPRpiO5B5fBmkgr6
|
|
|
9
10
|
cape_parsers/CAPE/community/CobaltStrikeStager.py,sha256=HLxROBjz453uHNq1bPz0VSAhtyWDfz79ZacTPdjuWmY,7535
|
|
10
11
|
cape_parsers/CAPE/community/DCRat.py,sha256=0-FRT3d2x63KQ_cs1xmKFj7x0JRf7ID6QDc_DvBa0PM,1003
|
|
11
12
|
cape_parsers/CAPE/community/Fareit.py,sha256=OyKeZdcvyAhjxZgJqkDPJHP4Npv1ArvTHJZ5F0C1Iac,1875
|
|
12
|
-
cape_parsers/CAPE/community/KoiLoader.py,sha256=
|
|
13
|
+
cape_parsers/CAPE/community/KoiLoader.py,sha256=tCl22pzNnuzC9dvlh13oa_KEBlMwchrEhy5KjipfyiM,4048
|
|
13
14
|
cape_parsers/CAPE/community/LokiBot.py,sha256=355kqLx0LNMr8XcGfPL7cxG8QZalcmE7ttVBqoWtTWE,5754
|
|
14
15
|
cape_parsers/CAPE/community/Lumma.py,sha256=Iqd9yvt3g0FeV_bYRmL1RKp4C1H92qeGg4fXivVDSxw,12206
|
|
15
16
|
cape_parsers/CAPE/community/MonsterV2.py,sha256=cFxhYxo7FruTMmFY3OtBO-E0hDyxfsC3zWX3BlcB-qI,2915
|
|
@@ -44,7 +45,7 @@ cape_parsers/CAPE/core/GuLoader.py,sha256=wH6t1e7rO60Bwe0ulqFdZq12-M087zT5WQtC_W
|
|
|
44
45
|
cape_parsers/CAPE/core/IcedID.py,sha256=TEsvFq8qHz_D5kIURKWSC4lbvWaQbMriDZ3jQsVu2VA,4029
|
|
45
46
|
cape_parsers/CAPE/core/IcedIDLoader.py,sha256=YUOEILpTycO01KK4qqAxGSplsRVs2EzjscUw4T-DGWs,1602
|
|
46
47
|
cape_parsers/CAPE/core/Latrodectus.py,sha256=1K9yUUYtzRJ2c3unrYIUaA8nE--Zoqi5pjXY7t7t1qg,7751
|
|
47
|
-
cape_parsers/CAPE/core/NitroBunnyDownloader.py,sha256=
|
|
48
|
+
cape_parsers/CAPE/core/NitroBunnyDownloader.py,sha256=EbQbfwGQXiOv2_cOK1-lcwbpKYo9t2cqHBv0FAw3keI,5257
|
|
48
49
|
cape_parsers/CAPE/core/Oyster.py,sha256=QStBScevJuLyd5d4Rw093SxTlbRG1LFkDwYgmjZx-EQ,4881
|
|
49
50
|
cape_parsers/CAPE/core/PikaBot.py,sha256=6Q8goXfMsSoU8UkdE9iuZY2KTxX_AmWhH1szke_HfWA,5280
|
|
50
51
|
cape_parsers/CAPE/core/PlugX.py,sha256=lGwr1T3mttG6CTbZCj_Cf5HnOad60A3LP264jlCsGsc,13192
|
|
@@ -111,7 +112,7 @@ cape_parsers/utils/blzpack_lib.so,sha256=5PJtnggw8fV5q4DlhwMJk4ZadvC3fFTsVTNZKvE
|
|
|
111
112
|
cape_parsers/utils/dotnet_utils.py,sha256=pzQGbCqccz7DRv8T_i1JURlrKDIlDT2axxViiFF9hsU,1672
|
|
112
113
|
cape_parsers/utils/lznt1.py,sha256=X-BmJtP6AwYSl0ORg5dfSt-NIuXbHrtCO5kUaaJI2C8,4066
|
|
113
114
|
cape_parsers/utils/strings.py,sha256=a-nbvP9jYST7b6t_H37Ype-fK2jEmQr-wMF5a4i04e4,3062
|
|
114
|
-
cape_parsers-0.1.
|
|
115
|
-
cape_parsers-0.1.
|
|
116
|
-
cape_parsers-0.1.
|
|
117
|
-
cape_parsers-0.1.
|
|
115
|
+
cape_parsers-0.1.56.dist-info/METADATA,sha256=ZF16inhJ6lLHbtRig0HNpioM6cBkYIeCILPFpcxfQbA,1826
|
|
116
|
+
cape_parsers-0.1.56.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
117
|
+
cape_parsers-0.1.56.dist-info/licenses/LICENSE,sha256=88c01_HLG8WPj7R7aU_b-O-UoF38vrrifvcko4KDxcE,1069
|
|
118
|
+
cape_parsers-0.1.56.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|