CAPE-parsers 0.1.42__py3-none-any.whl → 0.1.45__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/Lumma.py +38 -29
- {cape_parsers-0.1.42.dist-info → cape_parsers-0.1.45.dist-info}/METADATA +2 -2
- {cape_parsers-0.1.42.dist-info → cape_parsers-0.1.45.dist-info}/RECORD +5 -6
- cape_parsers/CAPE/community/Amadey.py +0 -43
- {cape_parsers-0.1.42.dist-info → cape_parsers-0.1.45.dist-info}/LICENSE +0 -0
- {cape_parsers-0.1.42.dist-info → cape_parsers-0.1.45.dist-info}/WHEEL +0 -0
|
@@ -75,7 +75,6 @@ RULE_SOURCE_LUMMA_NEW_ENCRYPTED_C2 = """rule LummaConfigNewEncryptedStrings
|
|
|
75
75
|
}"""
|
|
76
76
|
|
|
77
77
|
|
|
78
|
-
|
|
79
78
|
def yara_scan_generator(raw_data, rule_source):
|
|
80
79
|
yara_rules = yara.compile(source=rule_source)
|
|
81
80
|
matches = yara_rules.match(data=raw_data)
|
|
@@ -198,10 +197,22 @@ def chacha20_block(key, nonce, blocknum):
|
|
|
198
197
|
nonce_words = words_from_bytes(nonce)
|
|
199
198
|
|
|
200
199
|
original_block = [
|
|
201
|
-
constant_words[0],
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
200
|
+
constant_words[0],
|
|
201
|
+
constant_words[1],
|
|
202
|
+
constant_words[2],
|
|
203
|
+
constant_words[3],
|
|
204
|
+
key_words[0],
|
|
205
|
+
key_words[1],
|
|
206
|
+
key_words[2],
|
|
207
|
+
key_words[3],
|
|
208
|
+
key_words[4],
|
|
209
|
+
key_words[5],
|
|
210
|
+
key_words[6],
|
|
211
|
+
key_words[7],
|
|
212
|
+
mask32(blocknum),
|
|
213
|
+
nonce_words[0],
|
|
214
|
+
nonce_words[1],
|
|
215
|
+
nonce_words[2],
|
|
205
216
|
]
|
|
206
217
|
|
|
207
218
|
permuted_block = list(original_block)
|
|
@@ -241,7 +252,7 @@ def extract_c2_domain(data):
|
|
|
241
252
|
|
|
242
253
|
|
|
243
254
|
def find_encrypted_c2_blocks(data):
|
|
244
|
-
pattern = rb
|
|
255
|
+
pattern = rb"(.{128})\x00"
|
|
245
256
|
for match in re.findall(pattern, data, re.DOTALL):
|
|
246
257
|
yield match
|
|
247
258
|
|
|
@@ -251,9 +262,9 @@ def get_build_id(pe, data):
|
|
|
251
262
|
image_base = pe.OPTIONAL_HEADER.ImageBase
|
|
252
263
|
for offset in yara_scan_generator(data, RULE_SOURCE_BUILD_ID):
|
|
253
264
|
try:
|
|
254
|
-
build_id_data_rva = struct.unpack(
|
|
265
|
+
build_id_data_rva = struct.unpack("i", data[offset + 2 : offset + 6])[0]
|
|
255
266
|
build_id_dword_offset = pe.get_offset_from_rva(build_id_data_rva - image_base)
|
|
256
|
-
build_id_dword_rva = struct.unpack(
|
|
267
|
+
build_id_dword_rva = struct.unpack("i", data[build_id_dword_offset : build_id_dword_offset + 4])[0]
|
|
257
268
|
build_id_offset = pe.get_offset_from_rva(build_id_dword_rva - image_base)
|
|
258
269
|
build_id = pe.get_string_from_data(build_id_offset, data)
|
|
259
270
|
if not contains_non_printable(build_id):
|
|
@@ -263,18 +274,20 @@ def get_build_id(pe, data):
|
|
|
263
274
|
continue
|
|
264
275
|
return build_id
|
|
265
276
|
|
|
277
|
+
|
|
266
278
|
def get_build_id_new(data):
|
|
267
279
|
build_id = ""
|
|
268
|
-
pattern = b
|
|
280
|
+
pattern = b"123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ\x00"
|
|
269
281
|
offset = data.find(pattern)
|
|
270
282
|
if offset != -1:
|
|
271
|
-
build_id = data[offset + len(pattern):].split(b
|
|
283
|
+
build_id = data[offset + len(pattern) :].split(b"\x00", 1)[0]
|
|
272
284
|
build_id = build_id.decode()
|
|
273
285
|
|
|
274
286
|
return build_id
|
|
275
287
|
|
|
288
|
+
|
|
276
289
|
def extract_config(data):
|
|
277
|
-
config_dict = {
|
|
290
|
+
config_dict = {}
|
|
278
291
|
|
|
279
292
|
# try to load as a PE
|
|
280
293
|
pe = None
|
|
@@ -287,37 +300,36 @@ def extract_config(data):
|
|
|
287
300
|
key = None
|
|
288
301
|
nonce = None
|
|
289
302
|
for offset in yara_scan_generator(data, RULE_SOURCE_LUMMA_NEW_KEYS):
|
|
290
|
-
key_rva = struct.unpack(
|
|
303
|
+
key_rva = struct.unpack("i", data[offset + 1 : offset + 5])[0]
|
|
291
304
|
key_offset = pe.get_offset_from_rva(key_rva - image_base)
|
|
292
305
|
key = data[key_offset : key_offset + 32]
|
|
293
|
-
nonce_rva = struct.unpack(
|
|
306
|
+
nonce_rva = struct.unpack("i", data[offset + 20 : offset + 24])[0]
|
|
294
307
|
nonce_offset = pe.get_offset_from_rva(nonce_rva - image_base)
|
|
295
|
-
nonce = b
|
|
308
|
+
nonce = b"\x00\x00\x00\x00" + data[nonce_offset : nonce_offset + 8]
|
|
296
309
|
|
|
297
310
|
if key and nonce:
|
|
298
311
|
for offset in yara_scan_generator(data, RULE_SOURCE_LUMMA_NEW_ENCRYPTED_C2):
|
|
299
|
-
encrypted_strings_rva = struct.unpack(
|
|
312
|
+
encrypted_strings_rva = struct.unpack("i", data[offset + 5 : offset + 9])[0]
|
|
300
313
|
encrypted_strings_offset = pe.get_offset_from_rva(encrypted_strings_rva - image_base)
|
|
301
314
|
step_size = 0x80
|
|
302
315
|
counter = 2
|
|
303
316
|
for i in range(12):
|
|
304
|
-
encrypted_string = data[encrypted_strings_offset:encrypted_strings_offset+40]
|
|
305
|
-
decoded_c2 = chacha20_xor(encrypted_string, key, nonce, counter).split(b
|
|
317
|
+
encrypted_string = data[encrypted_strings_offset : encrypted_strings_offset + 40]
|
|
318
|
+
decoded_c2 = chacha20_xor(encrypted_string, key, nonce, counter).split(b"\x00", 1)[0]
|
|
306
319
|
if contains_non_printable(decoded_c2):
|
|
307
320
|
break
|
|
308
|
-
config_dict
|
|
321
|
+
config_dict.setdefault("C2", []).append(decoded_c2.decode())
|
|
309
322
|
encrypted_strings_offset = encrypted_strings_offset + step_size
|
|
310
323
|
counter += 2
|
|
311
324
|
|
|
312
|
-
if config_dict
|
|
325
|
+
if config_dict.get("C2"):
|
|
313
326
|
# If found C2 servers try to find build ID
|
|
314
327
|
build_id = get_build_id_new(data)
|
|
315
328
|
if build_id:
|
|
316
329
|
config_dict["Build ID"] = build_id
|
|
317
330
|
|
|
318
|
-
|
|
319
331
|
# If no C2s try with the version after Jan 21, 2025
|
|
320
|
-
if not config_dict
|
|
332
|
+
if "C2" not in config_dict:
|
|
321
333
|
offset = yara_scan(data, RULE_SOURCE_LUMMA)
|
|
322
334
|
if offset:
|
|
323
335
|
key = data[offset + 16 : offset + 48]
|
|
@@ -327,7 +339,7 @@ def extract_config(data):
|
|
|
327
339
|
try:
|
|
328
340
|
start_offset = offset + 56 + (i * 4)
|
|
329
341
|
end_offset = start_offset + 4
|
|
330
|
-
c2_dword_rva = struct.unpack(
|
|
342
|
+
c2_dword_rva = struct.unpack("i", data[start_offset:end_offset])[0]
|
|
331
343
|
if pe:
|
|
332
344
|
c2_dword_offset = pe.get_offset_from_rva(c2_dword_rva - image_base)
|
|
333
345
|
else:
|
|
@@ -345,16 +357,14 @@ def extract_config(data):
|
|
|
345
357
|
except Exception:
|
|
346
358
|
continue
|
|
347
359
|
|
|
348
|
-
if config_dict["C2"] and pe is not None:
|
|
360
|
+
if "C2" in config_dict and config_dict["C2"] and pe is not None:
|
|
349
361
|
# If found C2 servers try to find build ID
|
|
350
362
|
build_id = get_build_id(pe, data)
|
|
351
363
|
if build_id:
|
|
352
364
|
config_dict["Build ID"] = build_id
|
|
353
365
|
|
|
354
|
-
|
|
355
366
|
# If no C2s try with version prior to Jan 21, 2025
|
|
356
|
-
if not config_dict
|
|
357
|
-
|
|
367
|
+
if "C2" not in config_dict:
|
|
358
368
|
try:
|
|
359
369
|
if pe is not None:
|
|
360
370
|
rdata = get_rdata(pe, data)
|
|
@@ -374,20 +384,19 @@ def extract_config(data):
|
|
|
374
384
|
decoded_c2 = xor_data(encoded_c2, xor_key)
|
|
375
385
|
|
|
376
386
|
if not contains_non_printable(decoded_c2):
|
|
377
|
-
config_dict
|
|
387
|
+
config_dict.setdefault("C2", []).append(decoded_c2.decode())
|
|
378
388
|
except Exception:
|
|
379
389
|
continue
|
|
380
390
|
|
|
381
391
|
except Exception:
|
|
382
392
|
return
|
|
383
393
|
|
|
384
|
-
if
|
|
394
|
+
if "C2" in config_dict and pe is not None:
|
|
385
395
|
# If found C2 servers try to find build ID
|
|
386
396
|
build_id = get_build_id(pe, data)
|
|
387
397
|
if build_id:
|
|
388
398
|
config_dict["Build ID"] = build_id
|
|
389
399
|
|
|
390
|
-
|
|
391
400
|
return config_dict
|
|
392
401
|
|
|
393
402
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: CAPE-parsers
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.45
|
|
4
4
|
Summary: CAPE: Malware Configuration Extraction
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: cape,parsers,malware,configuration
|
|
@@ -23,7 +23,7 @@ Requires-Dist: pefile
|
|
|
23
23
|
Requires-Dist: pycryptodomex (>=3.20.0)
|
|
24
24
|
Requires-Dist: rat-king-parser (>=4.1.0)
|
|
25
25
|
Requires-Dist: ruff (>=0.7.2)
|
|
26
|
-
Requires-Dist: unicorn (
|
|
26
|
+
Requires-Dist: unicorn (>=2.1.1)
|
|
27
27
|
Requires-Dist: yara-python (>=4.5.1)
|
|
28
28
|
Description-Content-Type: text/markdown
|
|
29
29
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
cape_parsers/CAPE/__init__.py,sha256=JcY8WPKzUFYgexwV1eyKIuT1JyNZzMJjBynlPSzxY_I,7
|
|
2
2
|
cape_parsers/CAPE/community/AgentTesla.py,sha256=T1gUd28eoCGA5by3ylAAK1naenF0fE3jgYx7UBkCRDk,3559
|
|
3
|
-
cape_parsers/CAPE/community/Amadey.py,sha256=LuYt72sYa_c_srekD-H5hzZZQUlcGaeY1iT2HXO2YwE,1258
|
|
4
3
|
cape_parsers/CAPE/community/Arkei.py,sha256=kXn949PC2CksavsL1BgvKgiAUDcq2NQUirosCTQcDF0,3790
|
|
5
4
|
cape_parsers/CAPE/community/AsyncRAT.py,sha256=0nGLNnwnO93SPbCTgoIMvkh6_smuzQxDcYtL77afGx8,1001
|
|
6
5
|
cape_parsers/CAPE/community/AuroraStealer.py,sha256=UUoxgJtDan3fE1r8aDEKweC_URkV97QHBp1Hq_n7ShI,2419
|
|
@@ -15,7 +14,7 @@ cape_parsers/CAPE/community/Fareit.py,sha256=NYkcF7Ddf7SqaSJwGesGTumTJ2p8AT9qBE4
|
|
|
15
14
|
cape_parsers/CAPE/community/Greame.py,sha256=99W1aUoSNAQ9KMO85liel5rAN0Wutzo-m176iwfOzds,3633
|
|
16
15
|
cape_parsers/CAPE/community/KoiLoader.py,sha256=ZTDm7tGGNFyW8N9l35_ta7ucBuE5AL9YprNR36kfid8,4029
|
|
17
16
|
cape_parsers/CAPE/community/LokiBot.py,sha256=whdVVLqu760ai90Ep-_Ghc_Z1yaty9fMSOcnY5IajXc,5660
|
|
18
|
-
cape_parsers/CAPE/community/Lumma.py,sha256=
|
|
17
|
+
cape_parsers/CAPE/community/Lumma.py,sha256=0SxjHg61qvhXnqADEW3uS3aD_qbtPZUDKGhGsbNdQXE,12131
|
|
19
18
|
cape_parsers/CAPE/community/NanoCore.py,sha256=0dqhCoAyDJaYgAlbXIwCa1esfEuQSk5AtH1Rl4bj1l8,6120
|
|
20
19
|
cape_parsers/CAPE/community/Nighthawk.py,sha256=eXnDqwabnrlRROg503oXYLEgotMW4hKeYwLas8SrkTc,12104
|
|
21
20
|
cape_parsers/CAPE/community/Njrat.py,sha256=_noQM5058BYwTMcYCpcTD9gIxw4ANI35tUSLMAlN97Q,4713
|
|
@@ -107,7 +106,7 @@ cape_parsers/utils/blzpack_lib.so,sha256=5PJtnggw8fV5q4DlhwMJk4ZadvC3fFTsVTNZKvE
|
|
|
107
106
|
cape_parsers/utils/dotnet_utils.py,sha256=pzQGbCqccz7DRv8T_i1JURlrKDIlDT2axxViiFF9hsU,1672
|
|
108
107
|
cape_parsers/utils/lznt1.py,sha256=X-BmJtP6AwYSl0ORg5dfSt-NIuXbHrtCO5kUaaJI2C8,4066
|
|
109
108
|
cape_parsers/utils/strings.py,sha256=a-nbvP9jYST7b6t_H37Ype-fK2jEmQr-wMF5a4i04e4,3062
|
|
110
|
-
cape_parsers-0.1.
|
|
111
|
-
cape_parsers-0.1.
|
|
112
|
-
cape_parsers-0.1.
|
|
113
|
-
cape_parsers-0.1.
|
|
109
|
+
cape_parsers-0.1.45.dist-info/LICENSE,sha256=88c01_HLG8WPj7R7aU_b-O-UoF38vrrifvcko4KDxcE,1069
|
|
110
|
+
cape_parsers-0.1.45.dist-info/METADATA,sha256=pWGudbNe69KiEDcgrBiRneTOsE-pIWMCLdqa7Umj_pw,1149
|
|
111
|
+
cape_parsers-0.1.45.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
112
|
+
cape_parsers-0.1.45.dist-info/RECORD,,
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import base64
|
|
2
|
-
import re
|
|
3
|
-
|
|
4
|
-
str_hash_data = 'd6052c4fe86a6346964a6bbbe2423e20'
|
|
5
|
-
str_alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 '
|
|
6
|
-
|
|
7
|
-
def is_ascii(s):
|
|
8
|
-
return all(c < 128 or c == 0 for c in s)
|
|
9
|
-
|
|
10
|
-
def decrypt(str_data, str_hash_data, str_alphabet):
|
|
11
|
-
str_hash = ''
|
|
12
|
-
|
|
13
|
-
for i in range(len(str_data)):
|
|
14
|
-
str_hash += str_hash_data[i % len(str_hash_data)]
|
|
15
|
-
|
|
16
|
-
out = ''
|
|
17
|
-
|
|
18
|
-
for i in range(len(str_data)):
|
|
19
|
-
if str_data[i] not in str_alphabet:
|
|
20
|
-
out += str_data[i]
|
|
21
|
-
continue
|
|
22
|
-
alphabet_count = str_alphabet.find(str_data[i])
|
|
23
|
-
hash_count = str_alphabet.find(str_hash[i])
|
|
24
|
-
index_calc = (alphabet_count + len(str_alphabet) - hash_count) % len(str_alphabet)
|
|
25
|
-
out += str_alphabet[index_calc]
|
|
26
|
-
|
|
27
|
-
return base64.b64decode(out)
|
|
28
|
-
|
|
29
|
-
file_data = open('/tmp/amadey.bin','rb').read()
|
|
30
|
-
|
|
31
|
-
strings = []
|
|
32
|
-
for m in re.finditer(rb'[a-zA-Z =0-9]{4,}',file_data):
|
|
33
|
-
strings.append(m.group().decode('utf-8'))
|
|
34
|
-
|
|
35
|
-
for s in strings:
|
|
36
|
-
try:
|
|
37
|
-
temp = decrypt(s, str_hash_data, str_alphabet)
|
|
38
|
-
if is_ascii(temp) and len(temp) > 3:
|
|
39
|
-
print(temp.decode('utf-8'))
|
|
40
|
-
except:
|
|
41
|
-
continue
|
|
42
|
-
|
|
43
|
-
decrypt('1RydQIOr3Zcp6emn RYv8IGzgUKS6r5ThSdqDVBERAP2Ir 0JQ1=', str_hash_data, str_alphabet)
|
|
File without changes
|
|
File without changes
|