CAPE-parsers 0.1.42__py3-none-any.whl → 0.1.54__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.
Files changed (88) hide show
  1. cape_parsers/CAPE/community/AgentTesla.py +25 -10
  2. cape_parsers/CAPE/community/Amadey.py +199 -29
  3. cape_parsers/CAPE/community/Arkei.py +13 -15
  4. cape_parsers/CAPE/community/AsyncRAT.py +4 -2
  5. cape_parsers/CAPE/community/AuroraStealer.py +9 -6
  6. cape_parsers/CAPE/community/Carbanak.py +7 -7
  7. cape_parsers/CAPE/community/CobaltStrikeBeacon.py +5 -4
  8. cape_parsers/CAPE/community/CobaltStrikeStager.py +4 -1
  9. cape_parsers/CAPE/community/DCRat.py +4 -2
  10. cape_parsers/CAPE/community/Fareit.py +8 -9
  11. cape_parsers/CAPE/community/KoiLoader.py +3 -3
  12. cape_parsers/CAPE/community/LokiBot.py +11 -8
  13. cape_parsers/CAPE/community/Lumma.py +58 -40
  14. cape_parsers/CAPE/community/MonsterV2.py +93 -0
  15. cape_parsers/CAPE/community/MyKings.py +52 -0
  16. cape_parsers/CAPE/community/NanoCore.py +9 -9
  17. cape_parsers/CAPE/community/Nighthawk.py +1 -0
  18. cape_parsers/CAPE/community/Njrat.py +4 -4
  19. cape_parsers/CAPE/community/PhemedroneStealer.py +2 -0
  20. cape_parsers/CAPE/community/Snake.py +31 -18
  21. cape_parsers/CAPE/community/SparkRAT.py +3 -1
  22. cape_parsers/CAPE/community/Stealc.py +95 -63
  23. cape_parsers/CAPE/community/VenomRAT.py +4 -2
  24. cape_parsers/CAPE/community/WinosStager.py +75 -0
  25. cape_parsers/CAPE/community/XWorm.py +4 -2
  26. cape_parsers/CAPE/community/XenoRAT.py +4 -2
  27. cape_parsers/CAPE/core/AdaptixBeacon.py +7 -5
  28. cape_parsers/CAPE/core/AuraStealer.py +100 -0
  29. cape_parsers/CAPE/core/Azorult.py +5 -3
  30. cape_parsers/CAPE/core/BitPaymer.py +5 -2
  31. cape_parsers/CAPE/core/BlackDropper.py +10 -5
  32. cape_parsers/CAPE/core/Blister.py +12 -10
  33. cape_parsers/CAPE/core/BruteRatel.py +20 -7
  34. cape_parsers/CAPE/core/BumbleBee.py +34 -22
  35. cape_parsers/CAPE/core/DarkGate.py +3 -3
  36. cape_parsers/CAPE/core/DoppelPaymer.py +4 -2
  37. cape_parsers/CAPE/core/DridexLoader.py +4 -3
  38. cape_parsers/CAPE/core/Formbook.py +2 -2
  39. cape_parsers/CAPE/core/GuLoader.py +2 -5
  40. cape_parsers/CAPE/core/IcedID.py +5 -5
  41. cape_parsers/CAPE/core/IcedIDLoader.py +4 -4
  42. cape_parsers/CAPE/core/Latrodectus.py +14 -10
  43. cape_parsers/CAPE/core/NitroBunnyDownloader.py +151 -0
  44. cape_parsers/CAPE/core/Oyster.py +8 -6
  45. cape_parsers/CAPE/core/PikaBot.py +6 -6
  46. cape_parsers/CAPE/core/PlugX.py +3 -1
  47. cape_parsers/CAPE/core/QakBot.py +2 -1
  48. cape_parsers/CAPE/core/Quickbind.py +7 -11
  49. cape_parsers/CAPE/core/RedLine.py +2 -2
  50. cape_parsers/CAPE/core/Remcos.py +59 -51
  51. cape_parsers/CAPE/core/Rhadamanthys.py +175 -36
  52. cape_parsers/CAPE/core/SmokeLoader.py +2 -2
  53. cape_parsers/CAPE/core/Socks5Systemz.py +5 -5
  54. cape_parsers/CAPE/core/SquirrelWaffle.py +3 -3
  55. cape_parsers/CAPE/core/Strrat.py +1 -1
  56. cape_parsers/CAPE/core/WarzoneRAT.py +3 -2
  57. cape_parsers/CAPE/core/Zloader.py +21 -15
  58. cape_parsers/RATDecoders/test_rats.py +1 -0
  59. cape_parsers/__init__.py +14 -5
  60. cape_parsers/deprecated/BlackNix.py +59 -0
  61. cape_parsers/{CAPE/core → deprecated}/BuerLoader.py +1 -1
  62. cape_parsers/{CAPE/core → deprecated}/ChChes.py +3 -3
  63. cape_parsers/{CAPE/core → deprecated}/Enfal.py +1 -1
  64. cape_parsers/{CAPE/core → deprecated}/EvilGrab.py +5 -6
  65. cape_parsers/{CAPE/community → deprecated}/Greame.py +3 -1
  66. cape_parsers/{CAPE/core → deprecated}/HttpBrowser.py +7 -8
  67. cape_parsers/{CAPE/community → deprecated}/Pandora.py +2 -0
  68. cape_parsers/{CAPE/community → deprecated}/Punisher.py +2 -1
  69. cape_parsers/{CAPE/core → deprecated}/RCSession.py +7 -9
  70. cape_parsers/{CAPE/community → deprecated}/REvil.py +10 -5
  71. cape_parsers/{CAPE/core → deprecated}/RedLeaf.py +5 -7
  72. cape_parsers/{CAPE/community → deprecated}/Retefe.py +0 -2
  73. cape_parsers/{CAPE/community → deprecated}/Rozena.py +2 -5
  74. cape_parsers/{CAPE/community → deprecated}/SmallNet.py +6 -2
  75. {cape_parsers-0.1.42.dist-info → cape_parsers-0.1.54.dist-info}/METADATA +24 -3
  76. cape_parsers-0.1.54.dist-info/RECORD +117 -0
  77. {cape_parsers-0.1.42.dist-info → cape_parsers-0.1.54.dist-info}/WHEEL +1 -1
  78. cape_parsers/CAPE/community/BlackNix.py +0 -57
  79. cape_parsers/CAPE/core/Stealc.py +0 -21
  80. cape_parsers-0.1.42.dist-info/RECORD +0 -113
  81. /cape_parsers/{CAPE/community → deprecated}/BackOffLoader.py +0 -0
  82. /cape_parsers/{CAPE/community → deprecated}/BackOffPOS.py +0 -0
  83. /cape_parsers/{CAPE/core → deprecated}/Emotet.py +0 -0
  84. /cape_parsers/{CAPE/community → deprecated}/PoisonIvy.py +0 -0
  85. /cape_parsers/{CAPE/community → deprecated}/TSCookie.py +0 -0
  86. /cape_parsers/{CAPE/community → deprecated}/TrickBot.py +0 -0
  87. /cape_parsers/{CAPE/core → deprecated}/UrsnifV3.py +0 -0
  88. {cape_parsers-0.1.42.dist-info → cape_parsers-0.1.54.dist-info/licenses}/LICENSE +0 -0
@@ -5,7 +5,7 @@ import struct
5
5
  from contextlib import suppress
6
6
  import pefile
7
7
  import yara
8
-
8
+ from Cryptodome.Cipher import ChaCha20
9
9
 
10
10
  RULE_SOURCE_BUILD_ID = """rule LummaBuildId
11
11
  {
@@ -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)
@@ -143,7 +142,7 @@ def contains_non_printable(byte_array):
143
142
  return True
144
143
  return False
145
144
 
146
-
145
+ """
147
146
  def mask32(x):
148
147
  return x & 0xFFFFFFFF
149
148
 
@@ -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], constant_words[1], constant_words[2], constant_words[3],
202
- key_words[0], key_words[1], key_words[2], key_words[3],
203
- key_words[4], key_words[5], key_words[6], key_words[7],
204
- mask32(blocknum), nonce_words[0], nonce_words[1], nonce_words[2],
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)
@@ -231,7 +242,7 @@ def chacha20_xor(message, key, nonce, counter):
231
242
  xor_key.append(message[i] ^ key_stream[i])
232
243
 
233
244
  return xor_key
234
-
245
+ """
235
246
 
236
247
  def extract_c2_domain(data):
237
248
  pattern = rb"([\w-]+\.[\w]+)\x00"
@@ -241,7 +252,7 @@ def extract_c2_domain(data):
241
252
 
242
253
 
243
254
  def find_encrypted_c2_blocks(data):
244
- pattern = rb'(.{128})\x00'
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('i', data[offset + 2 : offset + 6])[0]
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('i', data[build_id_dword_offset : build_id_dword_offset + 4])[0]
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'123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ\x00'
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'\x00', 1)[0]
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 = {"C2": []}
290
+ config = {}
278
291
 
279
292
  # try to load as a PE
280
293
  pe = None
@@ -287,37 +300,38 @@ 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('i', data[offset + 1 : offset + 5])[0]
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('i', data[offset + 20 : offset + 24])[0]
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'\x00\x00\x00\x00' + data[nonce_offset : nonce_offset + 8]
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('i', data[offset + 5 : offset + 9])[0]
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'\x00', 1)[0]
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]
306
321
  if contains_non_printable(decoded_c2):
307
322
  break
308
- config_dict["C2"].append(decoded_c2.decode())
323
+ config.setdefault("CNCs", []).append("https://" + decoded_c2.decode())
309
324
  encrypted_strings_offset = encrypted_strings_offset + step_size
310
325
  counter += 2
311
326
 
312
- if config_dict["C2"]:
327
+ if config.get("CNCs"):
313
328
  # If found C2 servers try to find build ID
314
329
  build_id = get_build_id_new(data)
315
330
  if build_id:
316
- config_dict["Build ID"] = build_id
317
-
331
+ config["build"] = build_id
318
332
 
319
333
  # If no C2s try with the version after Jan 21, 2025
320
- if not config_dict["C2"]:
334
+ if "CNCs" not in config:
321
335
  offset = yara_scan(data, RULE_SOURCE_LUMMA)
322
336
  if offset:
323
337
  key = data[offset + 16 : offset + 48]
@@ -327,7 +341,7 @@ def extract_config(data):
327
341
  try:
328
342
  start_offset = offset + 56 + (i * 4)
329
343
  end_offset = start_offset + 4
330
- c2_dword_rva = struct.unpack('i', data[start_offset : end_offset])[0]
344
+ c2_dword_rva = struct.unpack("i", data[start_offset:end_offset])[0]
331
345
  if pe:
332
346
  c2_dword_offset = pe.get_offset_from_rva(c2_dword_rva - image_base)
333
347
  else:
@@ -336,25 +350,26 @@ def extract_config(data):
336
350
  c2_encrypted = data[c2_dword_offset : c2_dword_offset + 0x80]
337
351
  counters = [0, 2, 4, 6, 8, 10, 12, 14, 16]
338
352
  for counter in counters:
339
- decrypted = chacha20_xor(c2_encrypted, key, nonce, counter)
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]
340
357
  c2 = extract_c2_domain(decrypted)
341
358
  if c2 is not None and len(c2) > 10:
342
- config_dict["C2"].append(c2.decode())
359
+ config["CNCs"].append("https://" + c2.decode())
343
360
  break
344
361
 
345
362
  except Exception:
346
363
  continue
347
364
 
348
- if config_dict["C2"] and pe is not None:
365
+ if "CNCs" in config and config["CNCs"] and pe is not None:
349
366
  # If found C2 servers try to find build ID
350
367
  build_id = get_build_id(pe, data)
351
368
  if build_id:
352
- config_dict["Build ID"] = build_id
353
-
369
+ config["build"] = build_id
354
370
 
355
371
  # If no C2s try with version prior to Jan 21, 2025
356
- if not config_dict["C2"]:
357
-
372
+ if "CNCs" not in config:
358
373
  try:
359
374
  if pe is not None:
360
375
  rdata = get_rdata(pe, data)
@@ -374,21 +389,24 @@ def extract_config(data):
374
389
  decoded_c2 = xor_data(encoded_c2, xor_key)
375
390
 
376
391
  if not contains_non_printable(decoded_c2):
377
- config_dict["C2"].append(decoded_c2.decode())
378
- except Exception:
392
+ config.setdefault("CNCs", []).append("https://" + decoded_c2.decode())
393
+ except Exception as e:
394
+ print(e)
379
395
  continue
380
396
 
381
- except Exception:
397
+ except Exception as e:
398
+ print(e)
382
399
  return
383
400
 
384
- if config_dict["C2"] and pe is not None:
401
+ if "CNCs" in config and pe is not None:
385
402
  # If found C2 servers try to find build ID
386
403
  build_id = get_build_id(pe, data)
387
404
  if build_id:
388
- config_dict["Build ID"] = build_id
389
-
405
+ config["build"] = build_id
390
406
 
391
- return config_dict
407
+ print(config)
408
+ if config:
409
+ return config
392
410
 
393
411
 
394
412
  if __name__ == "__main__":
@@ -0,0 +1,93 @@
1
+ import hashlib
2
+ from Crypto.Cipher import ChaCha20_Poly1305
3
+ from contextlib import suppress
4
+ import zlib
5
+ import struct
6
+ import json
7
+ import yara
8
+ import pefile
9
+
10
+
11
+ RULE_SOURCE = """rule MonsterV2Config
12
+ {
13
+ meta:
14
+ author = "doomedraven,YungBinary"
15
+ strings:
16
+ $chunk_1 = {
17
+ 41 B8 0E 04 00 00
18
+ 48 8D 15 ?? ?? ?? 00
19
+ 48 8B C?
20
+ E8 ?? ?? ?? ?? [3-17]
21
+ 4C 8B C?
22
+ 48 8D 54 24 28
23
+ 48 8B CE
24
+ E8 ?? ?? ?? ??
25
+ }
26
+ condition:
27
+ $chunk_1
28
+ }"""
29
+
30
+
31
+ def derive_chacha_key_nonce_blake2b(seed: bytes): # -> tuple[bytes, bytes]:
32
+ """
33
+ Derives a 32-byte ChaCha20 key and a 24-byte ChaCha20 nonce
34
+ using BLAKE2b from a given seed.
35
+ """
36
+ output_length = 56 # 32 bytes for key + 24 bytes for nonce
37
+ h = hashlib.blake2b(digest_size=output_length)
38
+ h.update(seed)
39
+ derived_material = h.digest()
40
+ chacha20_key = derived_material[0:32]
41
+ chacha20_nonce = derived_material[32:56]
42
+ return chacha20_key, chacha20_nonce
43
+
44
+
45
+ def yara_scan(raw_data, rule_source):
46
+ yara_rules = yara.compile(source=rule_source)
47
+ matches = yara_rules.match(data=raw_data)
48
+
49
+ for match in matches:
50
+ for block in match.strings:
51
+ for instance in block.instances:
52
+ return instance.offset
53
+
54
+ def extract_config(data: bytes) -> dict:
55
+ config_dict = {}
56
+ with suppress(Exception):
57
+ pe = pefile.PE(data=data)
58
+ offset = yara_scan(data, RULE_SOURCE)
59
+
60
+ # image_base = pe.OPTIONAL_HEADER.ImageBase
61
+ disp_offset = data[offset + 9 : offset + 13]
62
+ disp_offset = struct.unpack('i', disp_offset)[0]
63
+ instruction_pointer_va = pe.get_rva_from_offset(offset + 13)
64
+ config_offset_va = instruction_pointer_va + disp_offset
65
+ config_offset = pe.get_offset_from_rva(config_offset_va)
66
+
67
+
68
+ blake_seed = data[config_offset : config_offset + 32]
69
+ chacha20_key, chacha20_nonce = derive_chacha_key_nonce_blake2b(blake_seed)
70
+ cipher_len = int.from_bytes(data[config_offset + 32 : config_offset + 40], byteorder="big")
71
+ cipher_text = data[config_offset + 40 : config_offset + 40 + cipher_len]
72
+
73
+ cipher = ChaCha20_Poly1305.new(key=chacha20_key, nonce=chacha20_nonce)
74
+ decrypted_zlib_data = cipher.decrypt(cipher_text)
75
+ decompressed_data = zlib.decompress(decrypted_zlib_data)
76
+ config_dict = json.loads(decompressed_data)
77
+
78
+ if config_dict:
79
+ final_config = {"raw": config_dict}
80
+ if "ip" in config_dict and "port" in config_dict:
81
+ final_config["CNCs"] = [f"tcp://{config_dict['ip']}:{config_dict['port']}"]
82
+ if "build_name" in config_dict:
83
+ final_config["build"] = config_dict["build_name"]
84
+ return final_config
85
+
86
+ return {}
87
+
88
+
89
+ if __name__ == "__main__":
90
+ import sys
91
+
92
+ with open(sys.argv[1], "rb") as f:
93
+ print(extract_config(f.read()))
@@ -0,0 +1,52 @@
1
+ """
2
+ Description: MyKings AKA Smominru config parser
3
+ Author: x.com/YungBinary
4
+ """
5
+
6
+ from contextlib import suppress
7
+ import json
8
+ import re
9
+ import base64
10
+
11
+
12
+ def contains_non_printable(byte_array):
13
+ for byte in byte_array:
14
+ if not chr(byte).isprintable():
15
+ return True
16
+ return False
17
+
18
+
19
+ def extract_base64_strings(data: bytes, minchars: int, maxchars: int) -> list:
20
+ pattern = b"([A-Za-z0-9+/=]{" + str(minchars).encode() + b"," + str(maxchars).encode() + b"})\x00{4}"
21
+ strings = []
22
+ for string in re.findall(pattern, data):
23
+ decoded_string = base64_and_printable(string.decode())
24
+ if decoded_string:
25
+ strings.append(decoded_string)
26
+ return strings
27
+
28
+
29
+ def base64_and_printable(b64_string: str):
30
+ with suppress(Exception):
31
+ decoded_bytes = base64.b64decode(b64_string)
32
+ if not contains_non_printable(decoded_bytes):
33
+ return decoded_bytes.decode('ascii')
34
+
35
+
36
+ def extract_config(data: bytes) -> dict:
37
+ config_dict = {}
38
+ with suppress(Exception):
39
+ cncs = extract_base64_strings(data, 12, 60)
40
+ if cncs:
41
+ # as they don't have schema they going under raw
42
+ config_dict["raw"] = {"CNCs": cncs}
43
+ return config_dict
44
+
45
+ return {}
46
+
47
+
48
+ if __name__ == "__main__":
49
+ import sys
50
+
51
+ with open(sys.argv[1], "rb") as f:
52
+ print(json.dumps(extract_config(f.read()), indent=4))
@@ -174,21 +174,21 @@ def extract_config(filebuf):
174
174
  pass
175
175
  elif DataType.DATETIME == param["type"]:
176
176
  dt = param["value"]
177
- config_dict[item_name] = dt.strftime("%Y-%m-%d %H:%M:%S.%f")
177
+ config_dict.setdefault("raw", {})[item_name] = dt.strftime("%Y-%m-%d %H:%M:%S.%f")
178
178
  else:
179
- config_dict[item_name] = str(param["value"])
179
+ config_dict.setdefault("raw", {})[item_name] = str(param["value"])
180
180
  except Exception as e:
181
181
  log.error("nanocore error: %s", e)
182
182
 
183
183
  cncs = []
184
184
 
185
- if config_dict.get("PrimaryConnectionHost"):
186
- cncs.append(config_dict["PrimaryConnectionHost"])
187
- if config_dict.get("PrimaryConnectionHost"):
188
- cncs.append(config_dict["BackupConnectionHost"])
189
- if config_dict.get("ConnectionPort") and cncs:
190
- port = config_dict["ConnectionPort"]
191
- config_dict["cncs"] = [f"{cnc}:{port}" for cnc in cncs]
185
+ if config_dict.get("raw", {}).get("PrimaryConnectionHost"):
186
+ cncs.append(config_dict["raw"]["PrimaryConnectionHost"])
187
+ if config_dict.get("raw", {}).get("PrimaryConnectionHost"):
188
+ cncs.append(config_dict["raw"]["BackupConnectionHost"])
189
+ if config_dict.get("raw", {}).get("ConnectionPort") and cncs:
190
+ port = config_dict["raw"]["ConnectionPort"]
191
+ config_dict["CNCs"] = [f"{cnc}:{port}" for cnc in cncs]
192
192
  return config_dict
193
193
 
194
194
 
@@ -4,6 +4,7 @@ import json
4
4
  import struct
5
5
 
6
6
  import pefile
7
+
7
8
  # import regex as re
8
9
  import re
9
10
  from Cryptodome.Cipher import AES
@@ -176,11 +176,11 @@ def extract_config(data):
176
176
  config = get_clean_config(config_dict)
177
177
  if config:
178
178
  if config.get("domain") and config.get("port"):
179
- conf["cncs"] = [f"{config['domain']}:{config['port']}"]
180
-
179
+ conf["CNCs"] = [f"{config['domain']}:{config['port']}"]
180
+
181
181
  if config.get("campaign_id"):
182
- conf["campaign id"] = config["campaign_id"]
183
-
182
+ conf["campaign"] = config["campaign_id"]
183
+
184
184
  if config.get("version"):
185
185
  conf["version"] = config["version"]
186
186
 
@@ -188,4 +188,6 @@ def extract_config(data):
188
188
  value_data = inst_.split(".")[-1].strip()
189
189
  config_field_name, str_list = check_next_inst(pe, body, DnfileParse, index)
190
190
  config_dict[config_field_name] = value_data
191
+ if config_dict:
192
+ config_dict = {"raw": config_dict}
191
193
  return config_dict
@@ -38,7 +38,7 @@ def handle_plain(dotnet_file, c2_type, user_strings):
38
38
  if c2_type == "Telegram":
39
39
  token = dotnet_file.net.user_strings.get(user_strings_list[15]).value.__str__()
40
40
  chat_id = dotnet_file.net.user_strings.get(user_strings_list[16]).value.__str__()
41
- return {"Type": "Telegram", "C2": f"https://api.telegram.org/bot{token}/sendMessage?chat_id={chat_id}"}
41
+ return {"raw": {"Type": "Telegram"}, "CNCs": f"https://api.telegram.org/bot{token}/sendMessage?chat_id={chat_id}"}
42
42
  elif c2_type == "SMTP":
43
43
  smtp_from = dotnet_file.net.user_strings.get(user_strings_list[7]).value.__str__()
44
44
  smtp_password = dotnet_file.net.user_strings.get(user_strings_list[8]).value.__str__()
@@ -46,18 +46,26 @@ def handle_plain(dotnet_file, c2_type, user_strings):
46
46
  smtp_to = dotnet_file.net.user_strings.get(user_strings_list[10]).value.__str__()
47
47
  smtp_port = dotnet_file.net.user_strings.get(user_strings_list[11]).value.__str__()
48
48
  return {
49
- "Type": "SMTP",
50
- "Host": smtp_host,
51
- "Port": smtp_port,
52
- "From Address": smtp_from,
53
- "To Address": smtp_to,
54
- "Password": smtp_password,
49
+ "raw": {
50
+ "Type": "SMTP",
51
+ "Host": smtp_host,
52
+ "Port": smtp_port,
53
+ "From Address": smtp_from,
54
+ "To Address": smtp_to,
55
+ "Password": smtp_password,
56
+ },
57
+ "CNCs": [f"smtp://{smtp_host}:{smtp_port}"]
55
58
  }
56
59
  elif c2_type == "FTP":
57
60
  ftp_username = dotnet_file.net.user_strings.get(user_strings_list[12]).value.__str__()
58
61
  ftp_password = dotnet_file.net.user_strings.get(user_strings_list[13]).value.__str__()
59
62
  ftp_host = dotnet_file.net.user_strings.get(user_strings_list[14]).value.__str__()
60
- return {"Type": "FTP", "Host": ftp_host, "Username": ftp_username, "Password": ftp_password}
63
+ return {
64
+ "raw": {
65
+ "Type": "FTP", "Host": ftp_host, "Username": ftp_username, "Password": ftp_password},
66
+ "CNCs": [f"ftp://{ftp_username}:{ftp_password}@{ftp_host}"]
67
+ }
68
+
61
69
 
62
70
 
63
71
  def handle_encrypted(dotnet_file, data, c2_type, user_strings):
@@ -98,20 +106,25 @@ def handle_encrypted(dotnet_file, data, c2_type, user_strings):
98
106
  if decrypted_strings:
99
107
  if c2_type == "Telegram":
100
108
  token, chat_id = decrypted_strings
101
- config_dict = {"Type": "Telegram", "C2": f"https://api.telegram.org/bot{token}/sendMessage?chat_id={chat_id}"}
109
+ config_dict = {"raw": {"Type": "Telegram"}, "CNCs": f"https://api.telegram.org/bot{token}/sendMessage?chat_id={chat_id}"}
102
110
  elif c2_type == "SMTP":
103
111
  smtp_from, smtp_password, smtp_host, smtp_to, smtp_port = decrypted_strings
104
112
  config_dict = {
105
- "Type": "SMTP",
106
- "Host": smtp_host,
107
- "Port": smtp_port,
108
- "From Address": smtp_from,
109
- "To Address": smtp_to,
110
- "Password": smtp_password,
113
+ "raw": {
114
+ "Type": "SMTP",
115
+ "Host": smtp_host,
116
+ "Port": smtp_port,
117
+ "From Address": smtp_from,
118
+ "To Address": smtp_to,
119
+ "Password": smtp_password,
120
+ }
111
121
  }
112
122
  elif c2_type == "FTP":
113
123
  ftp_username, ftp_password, ftp_host = decrypted_strings
114
- config_dict = {"Type": "FTP", "Host": ftp_host, "Username": ftp_username, "Password": ftp_password}
124
+ config_dict = {
125
+ "raw": {"Type": "FTP", "Host": ftp_host, "Username": ftp_username, "Password": ftp_password},
126
+ "CNCs": [f"ftp://{ftp_username}:{ftp_password}@{ftp_host}"]
127
+ }
115
128
  return config_dict
116
129
 
117
130
 
@@ -120,7 +133,7 @@ def extract_config(data):
120
133
  try:
121
134
  dotnet_file = dnfile.dnPE(data=data)
122
135
  except Exception as e:
123
- log.debug(f"Exception when attempting to parse .NET file: {e}")
136
+ log.debug("Exception when attempting to parse .NET file: %s", str(e))
124
137
  log.debug(traceback.format_exc())
125
138
 
126
139
  # ldstr, stsfld
@@ -152,7 +165,7 @@ def extract_config(data):
152
165
  else:
153
166
  user_strings[field_name] = string_index
154
167
  except Exception as e:
155
- log.debug(f"There was an exception parsing user strings: {e}")
168
+ log.debug("There was an exception parsing user strings: %s", str(e))
156
169
  log.debug(traceback.format_exc())
157
170
 
158
171
  if c2_type is None:
@@ -59,7 +59,9 @@ def extract_config(data):
59
59
  key = f.read(16)
60
60
  iv = f.read(16)
61
61
  enc_data = f.read(data_len - 32)
62
- return decrypt_config(enc_data, key, iv)
62
+ config = decrypt_config(enc_data, key, iv)
63
+ if config:
64
+ return {"raw": config}
63
65
  except Exception as e:
64
66
  log.error("Configuration decryption failed: %s", e)
65
67
  return {}