CAPE-parsers 0.1.54__py3-none-any.whl → 0.1.55__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.
@@ -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
- config["CNCs"] = find_c2(decoded_payload)
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
 
@@ -5,7 +5,7 @@ import struct
5
5
  from contextlib import suppress
6
6
  import pefile
7
7
  import yara
8
- from Cryptodome.Cipher import ChaCha20
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
- chacha20_cipher = ChaCha20.new(key=key, nonce=nonce)
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
- # decrypted = chacha20_xor(c2_encrypted, key, nonce, counter)
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
- $config = {E8 [3] 00 41 B8 ?? ?? 00 00 48 8D 15 [3] 00 48 89 C1 48 89 ?? E8 [3] 00}
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 "$config" in item.identifier:
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 + 7)
112
- config_offset = pe.get_dword_from_offset(config_code_offset + 14)
113
- rva = pe.get_rva_from_offset(config_code_offset + 18)
114
- config_rva = rva + config_offset
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: CAPE-parsers
3
- Version: 0.1.54
3
+ Version: 0.1.55
4
4
  Summary: CAPE: Malware Configuration Extraction
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -9,9 +9,9 @@ cape_parsers/CAPE/community/CobaltStrikeBeacon.py,sha256=U4Q0ObCrPRpiO5B5fBmkgr6
9
9
  cape_parsers/CAPE/community/CobaltStrikeStager.py,sha256=HLxROBjz453uHNq1bPz0VSAhtyWDfz79ZacTPdjuWmY,7535
10
10
  cape_parsers/CAPE/community/DCRat.py,sha256=0-FRT3d2x63KQ_cs1xmKFj7x0JRf7ID6QDc_DvBa0PM,1003
11
11
  cape_parsers/CAPE/community/Fareit.py,sha256=OyKeZdcvyAhjxZgJqkDPJHP4Npv1ArvTHJZ5F0C1Iac,1875
12
- cape_parsers/CAPE/community/KoiLoader.py,sha256=F2gsgCvrVuwxY1bg8rlexsjCjikAP5HIGGOqU8zhT8E,4008
12
+ cape_parsers/CAPE/community/KoiLoader.py,sha256=cWV3TcVz5Qg7kkmMYYtuh5cXhAKohGAk6d6aXuId0lc,4077
13
13
  cape_parsers/CAPE/community/LokiBot.py,sha256=355kqLx0LNMr8XcGfPL7cxG8QZalcmE7ttVBqoWtTWE,5754
14
- cape_parsers/CAPE/community/Lumma.py,sha256=0xwX6kOWR3ezrHxN1y1A5RD2-n8XRKx5hj3ynQjgRKU,12583
14
+ cape_parsers/CAPE/community/Lumma.py,sha256=Iqd9yvt3g0FeV_bYRmL1RKp4C1H92qeGg4fXivVDSxw,12206
15
15
  cape_parsers/CAPE/community/MonsterV2.py,sha256=cFxhYxo7FruTMmFY3OtBO-E0hDyxfsC3zWX3BlcB-qI,2915
16
16
  cape_parsers/CAPE/community/MyKings.py,sha256=bcypBMJf6Jeg0yrHZ-J-XjnhHvFbb-lpDF_zwW63gOk,1397
17
17
  cape_parsers/CAPE/community/NanoCore.py,sha256=8QZnf1AcY9481kSfsf3SHQShwPLn97peGAf8_xEasQc,6230
@@ -44,7 +44,7 @@ cape_parsers/CAPE/core/GuLoader.py,sha256=wH6t1e7rO60Bwe0ulqFdZq12-M087zT5WQtC_W
44
44
  cape_parsers/CAPE/core/IcedID.py,sha256=TEsvFq8qHz_D5kIURKWSC4lbvWaQbMriDZ3jQsVu2VA,4029
45
45
  cape_parsers/CAPE/core/IcedIDLoader.py,sha256=YUOEILpTycO01KK4qqAxGSplsRVs2EzjscUw4T-DGWs,1602
46
46
  cape_parsers/CAPE/core/Latrodectus.py,sha256=1K9yUUYtzRJ2c3unrYIUaA8nE--Zoqi5pjXY7t7t1qg,7751
47
- cape_parsers/CAPE/core/NitroBunnyDownloader.py,sha256=2HD7UOqZ8gtVxwR84OMCB4NSv0VqPAEMONuDSEgtDqk,4627
47
+ cape_parsers/CAPE/core/NitroBunnyDownloader.py,sha256=nxySGP7t5yv7-YL31-IiMxzkKCYKrDQgzWfMXadha2g,5265
48
48
  cape_parsers/CAPE/core/Oyster.py,sha256=QStBScevJuLyd5d4Rw093SxTlbRG1LFkDwYgmjZx-EQ,4881
49
49
  cape_parsers/CAPE/core/PikaBot.py,sha256=6Q8goXfMsSoU8UkdE9iuZY2KTxX_AmWhH1szke_HfWA,5280
50
50
  cape_parsers/CAPE/core/PlugX.py,sha256=lGwr1T3mttG6CTbZCj_Cf5HnOad60A3LP264jlCsGsc,13192
@@ -53,7 +53,7 @@ cape_parsers/CAPE/core/Quickbind.py,sha256=5A077RFQQOL8dtr2Q9vmlTKsWk96JkRWuHGse
53
53
  cape_parsers/CAPE/core/README.md,sha256=Zd84WEUj9NzKzGnVZV1jx6gMiEOtz01m32B7xEuS91k,17
54
54
  cape_parsers/CAPE/core/RedLine.py,sha256=bZeKLvxaS6HDpWY4RDXtSEBt93qTNzZG5iE6FNS0dOY,5734
55
55
  cape_parsers/CAPE/core/Remcos.py,sha256=MIpO2FwehBGIhO7hS0TT2hdDsgvxlI5ps4rAwyFwdTY,9483
56
- cape_parsers/CAPE/core/Rhadamanthys.py,sha256=kvkNj_Ydv_ewjctBNyBvJk31nRnXLl6pU18Ihq9bTXE,11259
56
+ cape_parsers/CAPE/core/Rhadamanthys.py,sha256=0vj3M1IC4oPISj1R7ELl9JZm1Uha9DTdbNJraJGdbh0,10725
57
57
  cape_parsers/CAPE/core/SmokeLoader.py,sha256=ruQ_GDiZvqtGxUTbN2N6fajUYWkIylFTvMXijgZ8L20,3890
58
58
  cape_parsers/CAPE/core/Socks5Systemz.py,sha256=jSt6QejL5K99dIB3qdItvUHL28w6N60xuwc8EQHM5Mk,783
59
59
  cape_parsers/CAPE/core/SquirrelWaffle.py,sha256=UMha7l60fL64VPHxueFUnCEGaO-CXau5ftEyK-Wv__o,3308
@@ -111,7 +111,7 @@ cape_parsers/utils/blzpack_lib.so,sha256=5PJtnggw8fV5q4DlhwMJk4ZadvC3fFTsVTNZKvE
111
111
  cape_parsers/utils/dotnet_utils.py,sha256=pzQGbCqccz7DRv8T_i1JURlrKDIlDT2axxViiFF9hsU,1672
112
112
  cape_parsers/utils/lznt1.py,sha256=X-BmJtP6AwYSl0ORg5dfSt-NIuXbHrtCO5kUaaJI2C8,4066
113
113
  cape_parsers/utils/strings.py,sha256=a-nbvP9jYST7b6t_H37Ype-fK2jEmQr-wMF5a4i04e4,3062
114
- cape_parsers-0.1.54.dist-info/METADATA,sha256=LH-LzvpGEEy_jgRIxzLAAvpmyqZAbL6f934-B4aEkGI,1826
115
- cape_parsers-0.1.54.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
116
- cape_parsers-0.1.54.dist-info/licenses/LICENSE,sha256=88c01_HLG8WPj7R7aU_b-O-UoF38vrrifvcko4KDxcE,1069
117
- cape_parsers-0.1.54.dist-info/RECORD,,
114
+ cape_parsers-0.1.55.dist-info/METADATA,sha256=eQvSMB-kC-_Hrdc78Sv-uxnNrVywaTqlLf_cjIG-gy8,1826
115
+ cape_parsers-0.1.55.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
116
+ cape_parsers-0.1.55.dist-info/licenses/LICENSE,sha256=88c01_HLG8WPj7R7aU_b-O-UoF38vrrifvcko4KDxcE,1069
117
+ cape_parsers-0.1.55.dist-info/RECORD,,