CAPE-parsers 0.1.44__tar.gz → 0.1.46__tar.gz

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 (117) hide show
  1. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/PKG-INFO +20 -1
  2. cape_parsers-0.1.46/README.md +23 -0
  3. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/community/AgentTesla.py +18 -9
  4. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/community/Arkei.py +13 -15
  5. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/community/AsyncRAT.py +4 -2
  6. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/community/AuroraStealer.py +9 -6
  7. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/community/Carbanak.py +7 -7
  8. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/community/CobaltStrikeBeacon.py +2 -1
  9. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/community/CobaltStrikeStager.py +4 -1
  10. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/community/DCRat.py +4 -2
  11. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/community/Fareit.py +8 -9
  12. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/community/KoiLoader.py +3 -3
  13. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/community/LokiBot.py +1 -1
  14. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/community/Lumma.py +49 -36
  15. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/community/NanoCore.py +9 -9
  16. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/community/Nighthawk.py +1 -0
  17. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/community/Njrat.py +4 -4
  18. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/community/PhemedroneStealer.py +2 -0
  19. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/community/Snake.py +29 -16
  20. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/community/SparkRAT.py +3 -1
  21. cape_parsers-0.1.46/cape_parsers/CAPE/community/Stealc.py +150 -0
  22. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/community/VenomRAT.py +4 -2
  23. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/community/XWorm.py +4 -2
  24. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/community/XenoRAT.py +4 -2
  25. cape_parsers-0.1.46/cape_parsers/CAPE/community/monsterv2.py +96 -0
  26. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/core/AdaptixBeacon.py +7 -5
  27. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/core/Azorult.py +5 -3
  28. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/core/BitPaymer.py +5 -2
  29. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/core/BlackDropper.py +10 -5
  30. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/core/Blister.py +12 -10
  31. cape_parsers-0.1.46/cape_parsers/CAPE/core/BruteRatel.py +41 -0
  32. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/core/BumbleBee.py +29 -17
  33. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/core/DarkGate.py +3 -3
  34. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/core/DoppelPaymer.py +4 -2
  35. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/core/DridexLoader.py +4 -3
  36. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/core/Formbook.py +2 -2
  37. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/core/GuLoader.py +2 -5
  38. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/core/IcedID.py +5 -5
  39. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/core/IcedIDLoader.py +4 -4
  40. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/core/Latrodectus.py +10 -7
  41. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/core/Oyster.py +8 -6
  42. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/core/PikaBot.py +6 -6
  43. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/core/PlugX.py +3 -1
  44. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/core/QakBot.py +2 -1
  45. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/core/Quickbind.py +7 -11
  46. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/core/RedLine.py +2 -2
  47. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/core/Remcos.py +58 -50
  48. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/core/Rhadamanthys.py +18 -8
  49. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/core/SmokeLoader.py +2 -2
  50. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/core/Socks5Systemz.py +5 -5
  51. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/core/SquirrelWaffle.py +3 -3
  52. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/core/Strrat.py +1 -1
  53. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/core/WarzoneRAT.py +3 -2
  54. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/core/Zloader.py +21 -15
  55. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/RATDecoders/test_rats.py +1 -0
  56. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/__init__.py +13 -4
  57. cape_parsers-0.1.46/cape_parsers/deprecated/BlackNix.py +59 -0
  58. {cape_parsers-0.1.44/cape_parsers/CAPE/core → cape_parsers-0.1.46/cape_parsers/deprecated}/BuerLoader.py +1 -1
  59. {cape_parsers-0.1.44/cape_parsers/CAPE/core → cape_parsers-0.1.46/cape_parsers/deprecated}/ChChes.py +3 -3
  60. {cape_parsers-0.1.44/cape_parsers/CAPE/core → cape_parsers-0.1.46/cape_parsers/deprecated}/Enfal.py +1 -1
  61. {cape_parsers-0.1.44/cape_parsers/CAPE/core → cape_parsers-0.1.46/cape_parsers/deprecated}/EvilGrab.py +5 -6
  62. {cape_parsers-0.1.44/cape_parsers/CAPE/community → cape_parsers-0.1.46/cape_parsers/deprecated}/Greame.py +3 -1
  63. {cape_parsers-0.1.44/cape_parsers/CAPE/core → cape_parsers-0.1.46/cape_parsers/deprecated}/HttpBrowser.py +7 -8
  64. {cape_parsers-0.1.44/cape_parsers/CAPE/community → cape_parsers-0.1.46/cape_parsers/deprecated}/Pandora.py +2 -0
  65. {cape_parsers-0.1.44/cape_parsers/CAPE/community → cape_parsers-0.1.46/cape_parsers/deprecated}/Punisher.py +2 -1
  66. {cape_parsers-0.1.44/cape_parsers/CAPE/core → cape_parsers-0.1.46/cape_parsers/deprecated}/RCSession.py +7 -9
  67. {cape_parsers-0.1.44/cape_parsers/CAPE/community → cape_parsers-0.1.46/cape_parsers/deprecated}/REvil.py +10 -5
  68. {cape_parsers-0.1.44/cape_parsers/CAPE/core → cape_parsers-0.1.46/cape_parsers/deprecated}/RedLeaf.py +5 -7
  69. {cape_parsers-0.1.44/cape_parsers/CAPE/community → cape_parsers-0.1.46/cape_parsers/deprecated}/Retefe.py +0 -2
  70. {cape_parsers-0.1.44/cape_parsers/CAPE/community → cape_parsers-0.1.46/cape_parsers/deprecated}/Rozena.py +2 -5
  71. {cape_parsers-0.1.44/cape_parsers/CAPE/community → cape_parsers-0.1.46/cape_parsers/deprecated}/SmallNet.py +6 -2
  72. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/pyproject.toml +1 -1
  73. cape_parsers-0.1.44/README.md +0 -4
  74. cape_parsers-0.1.44/cape_parsers/CAPE/community/BlackNix.py +0 -57
  75. cape_parsers-0.1.44/cape_parsers/CAPE/community/Stealc.py +0 -128
  76. cape_parsers-0.1.44/cape_parsers/CAPE/core/BruteRatel.py +0 -28
  77. cape_parsers-0.1.44/cape_parsers/CAPE/core/Stealc.py +0 -21
  78. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/LICENSE +0 -0
  79. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/__init__.py +0 -0
  80. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/community/QuasarRAT.py +0 -0
  81. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/community/README.md +0 -0
  82. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/community/__init__.py +0 -0
  83. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/core/README.md +0 -0
  84. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/core/__init__.py +0 -0
  85. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/CAPE/core/test_cape.py +0 -0
  86. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/RATDecoders/README.md +0 -0
  87. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/RATDecoders/__init__.py +0 -0
  88. {cape_parsers-0.1.44/cape_parsers/CAPE/community → cape_parsers-0.1.46/cape_parsers/deprecated}/BackOffLoader.py +0 -0
  89. {cape_parsers-0.1.44/cape_parsers/CAPE/community → cape_parsers-0.1.46/cape_parsers/deprecated}/BackOffPOS.py +0 -0
  90. {cape_parsers-0.1.44/cape_parsers/CAPE/core → cape_parsers-0.1.46/cape_parsers/deprecated}/Emotet.py +0 -0
  91. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/deprecated/Hancitor.py +0 -0
  92. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/deprecated/JavaDropper.py +0 -0
  93. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/deprecated/Nymaim.py +0 -0
  94. {cape_parsers-0.1.44/cape_parsers/CAPE/community → cape_parsers-0.1.46/cape_parsers/deprecated}/PoisonIvy.py +0 -0
  95. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/deprecated/PredatorPain.py +0 -0
  96. {cape_parsers-0.1.44/cape_parsers/CAPE/community → cape_parsers-0.1.46/cape_parsers/deprecated}/TSCookie.py +0 -0
  97. {cape_parsers-0.1.44/cape_parsers/CAPE/community → cape_parsers-0.1.46/cape_parsers/deprecated}/TrickBot.py +0 -0
  98. {cape_parsers-0.1.44/cape_parsers/CAPE/core → cape_parsers-0.1.46/cape_parsers/deprecated}/UrsnifV3.py +0 -0
  99. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/deprecated/_ShadowTech.py +0 -0
  100. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/deprecated/_VirusRat.py +0 -0
  101. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/deprecated/_jRat.py +0 -0
  102. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/deprecated/unrecom.py +0 -0
  103. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/deprecated/xRAT.py +0 -0
  104. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/malduck/LICENSE +0 -0
  105. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/malduck/README.md +0 -0
  106. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/malduck/__init__.py +0 -0
  107. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/malduck/test_malduck.py +0 -0
  108. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/mwcp/README.md +0 -0
  109. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/mwcp/__init__.py +0 -0
  110. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/mwcp/test_mwcp.py +0 -0
  111. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/utils/__init__.py +0 -0
  112. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/utils/aplib.py +0 -0
  113. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/utils/blzpack.py +0 -0
  114. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/utils/blzpack_lib.so +0 -0
  115. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/utils/dotnet_utils.py +0 -0
  116. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/utils/lznt1.py +0 -0
  117. {cape_parsers-0.1.44 → cape_parsers-0.1.46}/cape_parsers/utils/strings.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: CAPE-parsers
3
- Version: 0.1.44
3
+ Version: 0.1.46
4
4
  Summary: CAPE: Malware Configuration Extraction
5
5
  License: MIT
6
6
  Keywords: cape,parsers,malware,configuration
@@ -32,3 +32,22 @@ CAPE core and community parsers
32
32
 
33
33
  [![PyPI version](https://img.shields.io/pypi/v/CAPE-parsers)](https://pypi.org/project/CAPE-parsers/)
34
34
 
35
+ ### Configs structure
36
+ ```
37
+ CNCs: []
38
+ campaign: str
39
+ botnet: str
40
+ dga_seed: hex str
41
+ version: str
42
+ mutex: str
43
+ user_agent: str
44
+ build: str
45
+ cryptokey: str
46
+ cryptokey_type: str (algorithm). Ex: RC4, RSA public key. salsa20, (x)chacha20
47
+ raw: {any other data goes here}
48
+ ```
49
+ * All CNC entries should be in URL format. aka `<schema>://<hostname>:<port>/<uri>`
50
+ * Schema examples: `tcp://`, `ftp://`, `udp://`, `http(s)`, etc.
51
+ * Old CAPE configs still have lack of this structures as most of them are dead families.
52
+ * This CNC simplification make it easier to parse with tools like `tldextract` or `urlparse`
53
+
@@ -0,0 +1,23 @@
1
+ # CAPE-parsers
2
+ CAPE core and community parsers
3
+
4
+ [![PyPI version](https://img.shields.io/pypi/v/CAPE-parsers)](https://pypi.org/project/CAPE-parsers/)
5
+
6
+ ### Configs structure
7
+ ```
8
+ CNCs: []
9
+ campaign: str
10
+ botnet: str
11
+ dga_seed: hex str
12
+ version: str
13
+ mutex: str
14
+ user_agent: str
15
+ build: str
16
+ cryptokey: str
17
+ cryptokey_type: str (algorithm). Ex: RC4, RSA public key. salsa20, (x)chacha20
18
+ raw: {any other data goes here}
19
+ ```
20
+ * All CNC entries should be in URL format. aka `<schema>://<hostname>:<port>/<uri>`
21
+ * Schema examples: `tcp://`, `ftp://`, `udp://`, `http(s)`, etc.
22
+ * Old CAPE configs still have lack of this structures as most of them are dead families.
23
+ * This CNC simplification make it easier to parse with tools like `tldextract` or `urlparse`
@@ -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__":
@@ -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], 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)
@@ -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,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('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
+ 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["C2"].append(decoded_c2.decode())
321
+ config.setdefault("CNCs", []).append(decoded_c2.decode())
309
322
  encrypted_strings_offset = encrypted_strings_offset + step_size
310
323
  counter += 2
311
324
 
312
- if config_dict["C2"]:
325
+ if config.get("CNCs"):
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
- config_dict["Build ID"] = build_id
317
-
329
+ config["build"] = build_id
318
330
 
319
331
  # If no C2s try with the version after Jan 21, 2025
320
- if not config_dict["C2"]:
332
+ if "CNCs" not in config:
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('i', data[start_offset : end_offset])[0]
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:
@@ -339,22 +351,20 @@ def extract_config(data):
339
351
  decrypted = chacha20_xor(c2_encrypted, key, nonce, counter)
340
352
  c2 = extract_c2_domain(decrypted)
341
353
  if c2 is not None and len(c2) > 10:
342
- config_dict["C2"].append(c2.decode())
354
+ config["CNCs"].append(c2.decode())
343
355
  break
344
356
 
345
357
  except Exception:
346
358
  continue
347
359
 
348
- if config_dict["C2"] and pe is not None:
360
+ if "CNCs" in config and config["CNCs"] 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
- config_dict["Build ID"] = build_id
353
-
364
+ config["build"] = build_id
354
365
 
355
366
  # If no C2s try with version prior to Jan 21, 2025
356
- if not config_dict["C2"]:
357
-
367
+ if "CNCs" not in config:
358
368
  try:
359
369
  if pe is not None:
360
370
  rdata = get_rdata(pe, data)
@@ -374,21 +384,24 @@ 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["C2"].append(decoded_c2.decode())
378
- except Exception:
387
+ config.setdefault("CNCs", []).append(decoded_c2.decode())
388
+ except Exception as e:
389
+ print(e)
379
390
  continue
380
391
 
381
- except Exception:
392
+ except Exception as e:
393
+ print(e)
382
394
  return
383
395
 
384
- if config_dict["C2"] and pe is not None:
396
+ if "CNCs" in config and pe is not None:
385
397
  # If found C2 servers try to find build ID
386
398
  build_id = get_build_id(pe, data)
387
399
  if build_id:
388
- config_dict["Build ID"] = build_id
389
-
400
+ config["build"] = build_id
390
401
 
391
- return config_dict
402
+ print(config)
403
+ if config:
404
+ return config
392
405
 
393
406
 
394
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