CAPE-parsers 0.1.45__py3-none-any.whl → 0.1.46__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 (83) hide show
  1. cape_parsers/CAPE/community/AgentTesla.py +18 -9
  2. cape_parsers/CAPE/community/Arkei.py +13 -15
  3. cape_parsers/CAPE/community/AsyncRAT.py +4 -2
  4. cape_parsers/CAPE/community/AuroraStealer.py +9 -6
  5. cape_parsers/CAPE/community/Carbanak.py +7 -7
  6. cape_parsers/CAPE/community/CobaltStrikeBeacon.py +2 -1
  7. cape_parsers/CAPE/community/CobaltStrikeStager.py +4 -1
  8. cape_parsers/CAPE/community/DCRat.py +4 -2
  9. cape_parsers/CAPE/community/Fareit.py +8 -9
  10. cape_parsers/CAPE/community/KoiLoader.py +3 -3
  11. cape_parsers/CAPE/community/LokiBot.py +1 -1
  12. cape_parsers/CAPE/community/Lumma.py +19 -15
  13. cape_parsers/CAPE/community/NanoCore.py +9 -9
  14. cape_parsers/CAPE/community/Nighthawk.py +1 -0
  15. cape_parsers/CAPE/community/Njrat.py +4 -4
  16. cape_parsers/CAPE/community/PhemedroneStealer.py +2 -0
  17. cape_parsers/CAPE/community/Snake.py +29 -16
  18. cape_parsers/CAPE/community/SparkRAT.py +3 -1
  19. cape_parsers/CAPE/community/Stealc.py +86 -64
  20. cape_parsers/CAPE/community/VenomRAT.py +4 -2
  21. cape_parsers/CAPE/community/XWorm.py +4 -2
  22. cape_parsers/CAPE/community/XenoRAT.py +4 -2
  23. cape_parsers/CAPE/community/monsterv2.py +96 -0
  24. cape_parsers/CAPE/core/AdaptixBeacon.py +7 -5
  25. cape_parsers/CAPE/core/Azorult.py +5 -3
  26. cape_parsers/CAPE/core/BitPaymer.py +5 -2
  27. cape_parsers/CAPE/core/BlackDropper.py +10 -5
  28. cape_parsers/CAPE/core/Blister.py +12 -10
  29. cape_parsers/CAPE/core/BruteRatel.py +20 -7
  30. cape_parsers/CAPE/core/BumbleBee.py +29 -17
  31. cape_parsers/CAPE/core/DarkGate.py +3 -3
  32. cape_parsers/CAPE/core/DoppelPaymer.py +4 -2
  33. cape_parsers/CAPE/core/DridexLoader.py +4 -3
  34. cape_parsers/CAPE/core/Formbook.py +2 -2
  35. cape_parsers/CAPE/core/GuLoader.py +2 -5
  36. cape_parsers/CAPE/core/IcedID.py +5 -5
  37. cape_parsers/CAPE/core/IcedIDLoader.py +4 -4
  38. cape_parsers/CAPE/core/Latrodectus.py +10 -7
  39. cape_parsers/CAPE/core/Oyster.py +8 -6
  40. cape_parsers/CAPE/core/PikaBot.py +6 -6
  41. cape_parsers/CAPE/core/PlugX.py +3 -1
  42. cape_parsers/CAPE/core/QakBot.py +2 -1
  43. cape_parsers/CAPE/core/Quickbind.py +7 -11
  44. cape_parsers/CAPE/core/RedLine.py +2 -2
  45. cape_parsers/CAPE/core/Remcos.py +58 -50
  46. cape_parsers/CAPE/core/Rhadamanthys.py +18 -8
  47. cape_parsers/CAPE/core/SmokeLoader.py +2 -2
  48. cape_parsers/CAPE/core/Socks5Systemz.py +5 -5
  49. cape_parsers/CAPE/core/SquirrelWaffle.py +3 -3
  50. cape_parsers/CAPE/core/Strrat.py +1 -1
  51. cape_parsers/CAPE/core/WarzoneRAT.py +3 -2
  52. cape_parsers/CAPE/core/Zloader.py +21 -15
  53. cape_parsers/RATDecoders/test_rats.py +1 -0
  54. cape_parsers/__init__.py +13 -4
  55. cape_parsers/deprecated/BlackNix.py +59 -0
  56. cape_parsers/{CAPE/core → deprecated}/BuerLoader.py +1 -1
  57. cape_parsers/{CAPE/core → deprecated}/ChChes.py +3 -3
  58. cape_parsers/{CAPE/core → deprecated}/Enfal.py +1 -1
  59. cape_parsers/{CAPE/core → deprecated}/EvilGrab.py +5 -6
  60. cape_parsers/{CAPE/community → deprecated}/Greame.py +3 -1
  61. cape_parsers/{CAPE/core → deprecated}/HttpBrowser.py +7 -8
  62. cape_parsers/{CAPE/community → deprecated}/Pandora.py +2 -0
  63. cape_parsers/{CAPE/community → deprecated}/Punisher.py +2 -1
  64. cape_parsers/{CAPE/core → deprecated}/RCSession.py +7 -9
  65. cape_parsers/{CAPE/community → deprecated}/REvil.py +10 -5
  66. cape_parsers/{CAPE/core → deprecated}/RedLeaf.py +5 -7
  67. cape_parsers/{CAPE/community → deprecated}/Retefe.py +0 -2
  68. cape_parsers/{CAPE/community → deprecated}/Rozena.py +2 -5
  69. cape_parsers/{CAPE/community → deprecated}/SmallNet.py +6 -2
  70. {cape_parsers-0.1.45.dist-info → cape_parsers-0.1.46.dist-info}/METADATA +20 -1
  71. cape_parsers-0.1.46.dist-info/RECORD +112 -0
  72. cape_parsers/CAPE/community/BlackNix.py +0 -57
  73. cape_parsers/CAPE/core/Stealc.py +0 -21
  74. cape_parsers-0.1.45.dist-info/RECORD +0 -112
  75. /cape_parsers/{CAPE/community → deprecated}/BackOffLoader.py +0 -0
  76. /cape_parsers/{CAPE/community → deprecated}/BackOffPOS.py +0 -0
  77. /cape_parsers/{CAPE/core → deprecated}/Emotet.py +0 -0
  78. /cape_parsers/{CAPE/community → deprecated}/PoisonIvy.py +0 -0
  79. /cape_parsers/{CAPE/community → deprecated}/TSCookie.py +0 -0
  80. /cape_parsers/{CAPE/community → deprecated}/TrickBot.py +0 -0
  81. /cape_parsers/{CAPE/core → deprecated}/UrsnifV3.py +0 -0
  82. {cape_parsers-0.1.45.dist-info → cape_parsers-0.1.46.dist-info}/LICENSE +0 -0
  83. {cape_parsers-0.1.45.dist-info → cape_parsers-0.1.46.dist-info}/WHEEL +0 -0
@@ -6,7 +6,8 @@ except ImportError as e:
6
6
  print(f"Problem to import extract_strings: {e}")
7
7
 
8
8
 
9
- def extract_config(data):
9
+ def extract_config(data: bytes):
10
+ config = {}
10
11
  config_dict = {}
11
12
  with suppress(Exception):
12
13
  if data[:2] == b"MZ":
@@ -22,20 +23,21 @@ def extract_config(data):
22
23
  # Data Exfiltration via Telegram
23
24
  if "api.telegram.org" in lines[base + x]:
24
25
  config_dict["Protocol"] = "Telegram"
25
- config_dict["C2"] = lines[base + x]
26
+ config["CNCs"] = lines[base + x]
26
27
  config_dict["Password"] = lines[base + x + 1]
27
28
  break
28
29
  # Data Exfiltration via Discord
29
30
  elif "discord" in lines[base + x]:
30
31
  config_dict["Protocol"] = "Discord"
31
- config_dict["C2"] = lines[base + x]
32
+ config["CNCs"] = [lines[base + x]]
32
33
  break
33
34
  # Data Exfiltration via FTP
34
35
  elif "ftp:" in lines[base + x]:
35
36
  config_dict["Protocol"] = "FTP"
36
- config_dict["C2"] = lines[base + x]
37
- config_dict["Username"] = lines[base + x + 1]
38
- config_dict["Password"] = lines[base + x + 2]
37
+ hostname = lines[base + x]
38
+ username = lines[base + x + 1]
39
+ password = lines[base + x + 2]
40
+ config["CNCs"] = [f"ftp://{username}:{password}@{hostname}"]
39
41
  break
40
42
  # Data Exfiltration via SMTP
41
43
  elif "@" in lines[base + x]:
@@ -45,7 +47,7 @@ def extract_config(data):
45
47
  config_dict["Port"] = lines[base + x - 2]
46
48
  elif lines[base + x - 2] in {"true", "false"} and lines[base + x - 3].isdigit() and len(lines[base + x - 3]) <= 5:
47
49
  config_dict["Port"] = lines[base + x - 3]
48
- config_dict["C2"] = lines[base + +x - 1]
50
+ config_dict["CNCs"] = [lines[base + +x - 1]]
49
51
  config_dict["Username"] = lines[base + x]
50
52
  config_dict["Password"] = lines[base + x + 1]
51
53
  if "@" in lines[base + x + 2]:
@@ -72,6 +74,13 @@ def extract_config(data):
72
74
  for x in range(1, 8):
73
75
  if any(s in lines[base + index + x] for s in temp_match):
74
76
  config_dict["Protocol"] = "HTTP(S)"
75
- config_dict["C2"] = lines[base + index + x]
77
+ config["CNCs"] = lines[base + index + x]
76
78
  break
77
- return config_dict
79
+ if config or config_dict:
80
+ return config.setdefault("raw", config_dict)
81
+
82
+ if __name__ == "__main__":
83
+ import sys
84
+
85
+ with open(sys.argv[1], "rb") as f:
86
+ print(extract_config(f.read()))
@@ -1,7 +1,7 @@
1
1
  import struct
2
2
  import pefile
3
3
  import yara
4
-
4
+ from contextlib import suppress
5
5
 
6
6
  # Hash = 69ba4e2995d6b11bb319d7373d150560ea295c02773fe5aa9c729bfd2c334e1e
7
7
 
@@ -46,10 +46,10 @@ def xor_data(data, key):
46
46
 
47
47
 
48
48
  def extract_config(data):
49
- config_dict = {}
49
+ config = {}
50
50
 
51
51
  # Attempt to extract via old method
52
- try:
52
+ with suppress(Exception):
53
53
  domain = ""
54
54
  uri = ""
55
55
  lines = data.decode().split("\n")
@@ -59,14 +59,12 @@ def extract_config(data):
59
59
  if line.startswith("/") and line[-4] == ".":
60
60
  uri = line
61
61
  if domain and uri:
62
- config_dict.setdefault("C2", []).append(f"{domain}{uri}")
63
- return config_dict
64
- except Exception:
65
- pass
62
+ config.setdefault("CNCs", []).append(f"{domain}{uri}")
63
+ return config
66
64
 
67
65
  # Try with new method
68
66
 
69
- #config_dict["Strings"] = []
67
+ # config_dict["Strings"] = []
70
68
  pe = pefile.PE(data=data, fast_load=True)
71
69
  image_base = pe.OPTIONAL_HEADER.ImageBase
72
70
  domain = ""
@@ -84,17 +82,17 @@ def extract_config(data):
84
82
  if rule_str_name.startswith("$decode"):
85
83
  key_rva = data[str_decode_offset + 3 : str_decode_offset + 7]
86
84
  encoded_str_rva = data[str_decode_offset + 8 : str_decode_offset + 12]
87
- #dword_rva = data[str_decode_offset + 21 : str_decode_offset + 25]
85
+ # dword_rva = data[str_decode_offset + 21 : str_decode_offset + 25]
88
86
 
89
87
  key_offset = pe.get_offset_from_rva(struct.unpack("i", key_rva)[0] - image_base)
90
88
  encoded_str_offset = pe.get_offset_from_rva(struct.unpack("i", encoded_str_rva)[0] - image_base)
91
- #dword_offset = struct.unpack("i", dword_rva)[0]
92
- #dword_name = f"dword_{hex(dword_offset)[2:]}"
89
+ # dword_offset = struct.unpack("i", dword_rva)[0]
90
+ # dword_name = f"dword_{hex(dword_offset)[2:]}"
93
91
 
94
92
  key = data[key_offset : key_offset + str_size]
95
93
  encoded_str = data[encoded_str_offset : encoded_str_offset + str_size]
96
94
  decoded_str = xor_data(encoded_str, key).decode()
97
- #config_dict["Strings"].append({dword_name : decoded_str})
95
+ # config_dict["Strings"].append({dword_name : decoded_str})
98
96
 
99
97
  if last_str in ("http://", "https://"):
100
98
  domain += decoded_str
@@ -114,12 +112,12 @@ def extract_config(data):
114
112
  continue
115
113
 
116
114
  if domain and uri:
117
- config_dict.setdefault("C2", []).append(f"{domain}{uri}")
115
+ config.setdefault("CNCs", []).append(f"{domain}{uri}")
118
116
 
119
117
  if botnet_id:
120
- config_dict.setdefault("Botnet ID", botnet_id)
118
+ config.setdefault("botnet", botnet_id)
121
119
 
122
- return config_dict
120
+ return config
123
121
 
124
122
 
125
123
  if __name__ == "__main__":
@@ -5,10 +5,10 @@ import os
5
5
  from rat_king_parser.rkp import RATConfigParser
6
6
 
7
7
  HAVE_ASYNCRAT_COMMON = False
8
- module_file_path = '/opt/CAPEv2/data/asyncrat_common.py'
8
+ module_file_path = "/opt/CAPEv2/data/asyncrat_common.py"
9
9
  if os.path.exists(module_file_path):
10
10
  try:
11
- module_name = os.path.basename(module_file_path).replace('.py', '')
11
+ module_name = os.path.basename(module_file_path).replace(".py", "")
12
12
  spec = importlib.util.spec_from_file_location(module_name, module_file_path)
13
13
  asyncrat_common = importlib.util.module_from_spec(spec)
14
14
  sys.modules[module_name] = asyncrat_common
@@ -17,6 +17,7 @@ if os.path.exists(module_file_path):
17
17
  except Exception as e:
18
18
  print("Error loading asyncrat_common.py", e)
19
19
 
20
+
20
21
  def extract_config(data: bytes):
21
22
  config = RATConfigParser(data=data, remap_config=True).report.get("config", {})
22
23
  if config and HAVE_ASYNCRAT_COMMON:
@@ -24,6 +25,7 @@ def extract_config(data: bytes):
24
25
 
25
26
  return config
26
27
 
28
+
27
29
  if __name__ == "__main__":
28
30
  data = open(sys.argv[1], "rb").read()
29
31
  print(extract_config(data))
@@ -32,9 +32,12 @@ def extract_config(data):
32
32
  key = item.split(":")[0].strip("{").strip('"')
33
33
  value = item.split(":")[1].strip('"')
34
34
  if key == "IP":
35
- key = "C2"
36
- if value:
37
- config_dict[key] = value
35
+ config_dict["CNCs"] = [value]
36
+ elif key == "BuildID":
37
+ config_dict["build"] = value
38
+ else:
39
+ if value:
40
+ config_dict.setdefault("raw", {})[key] = value
38
41
 
39
42
  grabber_found = False
40
43
 
@@ -47,13 +50,13 @@ def extract_config(data):
47
50
  data_dict = json.loads(decoded_str)
48
51
  for elem in data_dict:
49
52
  if elem["Method"] == "DW":
50
- config_dict["Loader module"] = elem
53
+ config_dict.setdefault("raw", {})["Loader module"] = elem
51
54
 
52
55
  if b"PS" in decoded_str:
53
56
  data_dict = json.loads(decoded_str)
54
57
  for elem in data_dict:
55
58
  if elem["Method"] == "PS":
56
- config_dict["PowerShell module"] = elem
59
+ config_dict.setdefault("raw", {})["PowerShell module"] = elem
57
60
 
58
61
  if b"Path" in decoded_str:
59
62
  grabber_found = True
@@ -68,6 +71,6 @@ def extract_config(data):
68
71
 
69
72
  if not grabber_found:
70
73
  grabber_found = True
71
- config_dict["Grabber"] = cleanup_str
74
+ config_dict.setdefault("raw", {})["Grabber"] = cleanup_str
72
75
 
73
76
  return config_dict
@@ -158,22 +158,22 @@ def extract_config(filebuf):
158
158
  if dec:
159
159
  ver = re.findall(r"^(\d+\.\d+)$", dec)
160
160
  if ver:
161
- cfg["Version"] = ver[0]
161
+ cfg["version"] = ver[0]
162
162
 
163
163
  data = data_sections[0].get_data()
164
164
  items = data.split(b"\x00")
165
165
 
166
166
  with suppress(IndexError, UnicodeDecodeError, ValueError):
167
- cfg["Unknown 1"] = decode_string(items[0], sbox).decode("utf8")
168
- cfg["Unknown 2"] = decode_string(items[8], sbox).decode("utf8")
167
+ cfg.setdefault("raw", {})["Unknown 1"] = decode_string(items[0], sbox).decode("utf8")
168
+ cfg.setdefault("raw", {})["Unknown 2"] = decode_string(items[8], sbox).decode("utf8")
169
169
  c2_dec = decode_string(items[10], sbox).decode("utf8")
170
170
  if "|" in c2_dec:
171
171
  c2_dec = c2_dec.split("|")
172
- cfg["C2"] = c2_dec
173
- if float(cfg["Version"]) < 1.7:
174
- cfg["Campaign Id"] = decode_string(items[276], sbox).decode("utf8")
172
+ cfg["CNCs"] = c2_dec
173
+ if float(cfg["version"]) < 1.7:
174
+ cfg["campaign"] = decode_string(items[276], sbox).decode("utf8")
175
175
  else:
176
- cfg["Campaign Id"] = decode_string(items[25], sbox).decode("utf8")
176
+ cfg["campaign"] = decode_string(items[25], sbox).decode("utf8")
177
177
 
178
178
  return cfg
179
179
 
@@ -460,4 +460,5 @@ def extract_config(data):
460
460
  output = cobaltstrikeConfig(data).parse_config(as_json=True)
461
461
  if output is None:
462
462
  output = cobaltstrikeConfig(data).parse_encrypted_config(as_json=True)
463
- return output
463
+ if output:
464
+ return {"raw": output}
@@ -187,4 +187,7 @@ class StagerConfig:
187
187
 
188
188
  def extract_config(data):
189
189
  """Config extraction function for CapeV2"""
190
- return StagerConfig(data).get_config()
190
+ config = StagerConfig(data).get_config()
191
+ if config:
192
+ return {"raw": config}
193
+ return {}
@@ -5,10 +5,10 @@ import os
5
5
  from rat_king_parser.rkp import RATConfigParser
6
6
 
7
7
  HAVE_ASYNCRAT_COMMON = False
8
- module_file_path = '/opt/CAPEv2/data/asyncrat_common.py'
8
+ module_file_path = "/opt/CAPEv2/data/asyncrat_common.py"
9
9
  if os.path.exists(module_file_path):
10
10
  try:
11
- module_name = os.path.basename(module_file_path).replace('.py', '')
11
+ module_name = os.path.basename(module_file_path).replace(".py", "")
12
12
  spec = importlib.util.spec_from_file_location(module_name, module_file_path)
13
13
  asyncrat_common = importlib.util.module_from_spec(spec)
14
14
  sys.modules[module_name] = asyncrat_common
@@ -17,6 +17,7 @@ if os.path.exists(module_file_path):
17
17
  except Exception as e:
18
18
  print("Error loading asyncrat_common.py", e)
19
19
 
20
+
20
21
  def extract_config(data: bytes):
21
22
  config = RATConfigParser(data=data, remap_config=True).report.get("config", {})
22
23
  if config and HAVE_ASYNCRAT_COMMON:
@@ -24,6 +25,7 @@ def extract_config(data: bytes):
24
25
 
25
26
  return config
26
27
 
28
+
27
29
  if __name__ == "__main__":
28
30
  data = open(sys.argv[1], "rb").read()
29
31
  print(extract_config(data))
@@ -35,10 +35,8 @@ def extract_config(memdump_path, read=False):
35
35
  if buf and len(buf[0]) > 200:
36
36
  cData = buf[0][200:]
37
37
  """
38
- artifacts_raw = {
39
- "controllers": [],
40
- "downloads": [],
41
- }
38
+ config = {}
39
+ artifacts_raw = {}
42
40
 
43
41
  start = F.find(b"YUIPWDFILE0YUIPKDFILE0YUICRYPTED0YUI1.0")
44
42
  if start:
@@ -53,15 +51,16 @@ def extract_config(memdump_path, read=False):
53
51
  # url = self._check_valid_url(url)
54
52
  if url is None:
55
53
  continue
54
+ url = url.lower()
56
55
  if gate_url.match(url):
57
- artifacts_raw["controllers"].append(url.lower().decode())
56
+ config.setdefault("CNCs", []).append(url.decode())
58
57
  elif exe_url.match(url) or dll_url.match(url):
59
- artifacts_raw["downloads"].append(url.lower().decode())
58
+ artifacts_raw["downloads"].append(url.decode())
60
59
  except Exception as e:
61
60
  print(e, sys.exc_info(), "PONY")
62
- artifacts_raw["controllers"] = list(set(artifacts_raw["controllers"]))
63
- artifacts_raw["downloads"] = list(set(artifacts_raw["downloads"]))
64
- return artifacts_raw if len(artifacts_raw["controllers"]) != 0 or len(artifacts_raw["downloads"]) != 0 else False
61
+ config["CNCs"] = list(set(config["controllers"]))
62
+ config.setdefault("raw", {})["downloads"] = list(set(artifacts_raw["downloads"]))
63
+ return config
65
64
 
66
65
 
67
66
  if __name__ == "__main__":
@@ -89,7 +89,7 @@ def xor_data(data, key):
89
89
 
90
90
 
91
91
  def extract_config(data):
92
- config_dict = {"C2": []}
92
+ config = {}
93
93
 
94
94
  xor_key = b""
95
95
  encoded_payload = b""
@@ -119,9 +119,9 @@ 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
 
122
- config_dict["C2"] = find_c2(decoded_payload)
122
+ config["CNCs"] = find_c2(decoded_payload)
123
123
 
124
- return config_dict
124
+ return config
125
125
 
126
126
 
127
127
  if __name__ == "__main__":
@@ -158,7 +158,7 @@ def decoder(data):
158
158
  def extract_config(filebuf):
159
159
 
160
160
  urls = decoder(filebuf)
161
- return {"address": [url.decode() for url in urls]}
161
+ return {"CNCs": [url.decode() for url in urls]}
162
162
 
163
163
 
164
164
  if __name__ == "__main__":
@@ -287,7 +287,7 @@ def get_build_id_new(data):
287
287
 
288
288
 
289
289
  def extract_config(data):
290
- config_dict = {}
290
+ config = {}
291
291
 
292
292
  # try to load as a PE
293
293
  pe = None
@@ -318,18 +318,18 @@ def extract_config(data):
318
318
  decoded_c2 = chacha20_xor(encrypted_string, key, nonce, counter).split(b"\x00", 1)[0]
319
319
  if contains_non_printable(decoded_c2):
320
320
  break
321
- config_dict.setdefault("C2", []).append(decoded_c2.decode())
321
+ config.setdefault("CNCs", []).append(decoded_c2.decode())
322
322
  encrypted_strings_offset = encrypted_strings_offset + step_size
323
323
  counter += 2
324
324
 
325
- if config_dict.get("C2"):
325
+ if config.get("CNCs"):
326
326
  # If found C2 servers try to find build ID
327
327
  build_id = get_build_id_new(data)
328
328
  if build_id:
329
- config_dict["Build ID"] = build_id
329
+ config["build"] = build_id
330
330
 
331
331
  # If no C2s try with the version after Jan 21, 2025
332
- if "C2" not in config_dict:
332
+ if "CNCs" not in config:
333
333
  offset = yara_scan(data, RULE_SOURCE_LUMMA)
334
334
  if offset:
335
335
  key = data[offset + 16 : offset + 48]
@@ -351,20 +351,20 @@ def extract_config(data):
351
351
  decrypted = chacha20_xor(c2_encrypted, key, nonce, counter)
352
352
  c2 = extract_c2_domain(decrypted)
353
353
  if c2 is not None and len(c2) > 10:
354
- config_dict["C2"].append(c2.decode())
354
+ config["CNCs"].append(c2.decode())
355
355
  break
356
356
 
357
357
  except Exception:
358
358
  continue
359
359
 
360
- if "C2" in config_dict and config_dict["C2"] and pe is not None:
360
+ if "CNCs" in config and config["CNCs"] and pe is not None:
361
361
  # If found C2 servers try to find build ID
362
362
  build_id = get_build_id(pe, data)
363
363
  if build_id:
364
- config_dict["Build ID"] = build_id
364
+ config["build"] = build_id
365
365
 
366
366
  # If no C2s try with version prior to Jan 21, 2025
367
- if "C2" not in config_dict:
367
+ if "CNCs" not in config:
368
368
  try:
369
369
  if pe is not None:
370
370
  rdata = get_rdata(pe, data)
@@ -384,20 +384,24 @@ def extract_config(data):
384
384
  decoded_c2 = xor_data(encoded_c2, xor_key)
385
385
 
386
386
  if not contains_non_printable(decoded_c2):
387
- config_dict.setdefault("C2", []).append(decoded_c2.decode())
388
- except Exception:
387
+ config.setdefault("CNCs", []).append(decoded_c2.decode())
388
+ except Exception as e:
389
+ print(e)
389
390
  continue
390
391
 
391
- except Exception:
392
+ except Exception as e:
393
+ print(e)
392
394
  return
393
395
 
394
- if "C2" in config_dict and pe is not None:
396
+ if "CNCs" in config and pe is not None:
395
397
  # If found C2 servers try to find build ID
396
398
  build_id = get_build_id(pe, data)
397
399
  if build_id:
398
- config_dict["Build ID"] = build_id
400
+ config["build"] = build_id
399
401
 
400
- return config_dict
402
+ print(config)
403
+ if config:
404
+ return config
401
405
 
402
406
 
403
407
  if __name__ == "__main__":
@@ -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
 
@@ -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 {}