CAPE-parsers 0.1.44__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 +49 -36
  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.44.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.44.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.44.dist-info → cape_parsers-0.1.46.dist-info}/LICENSE +0 -0
  83. {cape_parsers-0.1.44.dist-info → cape_parsers-0.1.46.dist-info}/WHEEL +0 -0
@@ -11,12 +11,15 @@ AUTHOR = "kevoreilly, YungBinary"
11
11
  def mask32(x):
12
12
  return x & 0xFFFFFFFF
13
13
 
14
+
14
15
  def add32(x, y):
15
16
  return mask32(x + y)
16
17
 
18
+
17
19
  def left_rotate(x, n):
18
20
  return mask32(x << n) | (x >> (32 - n))
19
21
 
22
+
20
23
  def quarter_round(block, a, b, c, d):
21
24
  block[a] = add32(block[a], block[b])
22
25
  block[d] ^= block[a]
@@ -31,6 +34,7 @@ def quarter_round(block, a, b, c, d):
31
34
  block[b] ^= block[c]
32
35
  block[b] = left_rotate(block[b], 7)
33
36
 
37
+
34
38
  def chacha20_permute(block):
35
39
  for doubleround in range(10):
36
40
  quarter_round(block, 0, 4, 8, 12)
@@ -42,6 +46,7 @@ def chacha20_permute(block):
42
46
  quarter_round(block, 2, 7, 8, 13)
43
47
  quarter_round(block, 3, 4, 9, 14)
44
48
 
49
+
45
50
  def words_from_bytes(b):
46
51
  assert len(b) % 4 == 0
47
52
  return [int.from_bytes(b[4 * i : 4 * i + 4], "little") for i in range(len(b) // 4)]
@@ -50,11 +55,12 @@ def words_from_bytes(b):
50
55
  def bytes_from_words(w):
51
56
  return b"".join(word.to_bytes(4, "little") for word in w)
52
57
 
58
+
53
59
  def chacha20_block(key, nonce, blocknum):
54
60
  # This implementation doesn't support 16-byte keys.
55
61
  assert len(key) == 32
56
62
  assert len(nonce) == 12
57
- assert blocknum < 2 ** 32
63
+ assert blocknum < 2**32
58
64
  constant_words = words_from_bytes(b"expand 32-byte k")
59
65
  key_words = words_from_bytes(key)
60
66
  nonce_words = words_from_bytes(nonce)
@@ -72,6 +78,7 @@ def chacha20_block(key, nonce, blocknum):
72
78
  permuted_block[i] = add32(permuted_block[i], original_block[i])
73
79
  return bytes_from_words(permuted_block)
74
80
 
81
+
75
82
  def chacha20_stream(key, nonce, length, blocknum):
76
83
  output = bytearray()
77
84
  while length > 0:
@@ -82,6 +89,7 @@ def chacha20_stream(key, nonce, length, blocknum):
82
89
  blocknum += 1
83
90
  return output
84
91
 
92
+
85
93
  def decrypt_config(data):
86
94
  decrypted_config = b"\x21\x52\x48\x59"
87
95
  data_len = len(data)
@@ -98,6 +106,7 @@ def decrypt_config(data):
98
106
  v8 -= 1
99
107
  v3 += 1
100
108
 
109
+
101
110
  def chacha20_xor(custom_b64_decoded, key, nonce):
102
111
  message_len = len(custom_b64_decoded)
103
112
  key_stream = chacha20_stream(key, nonce, message_len, 0x80)
@@ -108,6 +117,7 @@ def chacha20_xor(custom_b64_decoded, key, nonce):
108
117
 
109
118
  return xor_key
110
119
 
120
+
111
121
  def extract_strings(data, minchars, maxchars):
112
122
  apat = b"([\x20-\x7e]{" + str(minchars).encode() + b"," + str(maxchars).encode() + b"})\x00"
113
123
  strings = [string.decode() for string in re.findall(apat, data)]
@@ -118,11 +128,13 @@ def extract_strings(data, minchars, maxchars):
118
128
  strings.extend(str(ws.decode("utf-16le")) for ws in re.findall(upat, data))
119
129
  return strings
120
130
 
131
+
121
132
  def extract_c2_url(data):
122
133
  pattern = b"(http[\x20-\x7e]+)\x00"
123
134
  match = re.search(pattern, data)
124
135
  return match.group(1).decode()
125
136
 
137
+
126
138
  def is_potential_custom_base64(string):
127
139
  custom_alphabet = "ABC1fghijklmnop234NOPQRSTUVWXY567DEFGHIJKLMZ089abcdeqrstuvwxyz-|"
128
140
  for c in string:
@@ -130,6 +142,7 @@ def is_potential_custom_base64(string):
130
142
  return False
131
143
  return True
132
144
 
145
+
133
146
  def custom_b64decode(data):
134
147
  """Decodes base64 data using a custom alphabet."""
135
148
  standard_alphabet = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
@@ -138,11 +151,12 @@ def custom_b64decode(data):
138
151
  table = bytes.maketrans(custom_alphabet, standard_alphabet)
139
152
  return base64.b64decode(data.translate(table), validate=True)
140
153
 
154
+
141
155
  def extract_config(data):
142
156
  config_dict = {}
143
157
  magic = struct.unpack("I", data[:4])[0]
144
158
  if magic == 0x59485221:
145
- config_dict["C2"] = data[24:].split(b"\0", 1)[0].decode()
159
+ config_dict["CNCs"] = [data[24:].split(b"\0", 1)[0].decode()]
146
160
  return config_dict
147
161
  else:
148
162
  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"
@@ -157,15 +171,12 @@ def extract_config(data):
157
171
  custom_b64_decoded = custom_b64decode(string)
158
172
  xor_key = chacha20_xor(custom_b64_decoded, key, nonce)
159
173
  decrypted_config = decrypt_config(xor_key)
160
- reexecution_delay = int.from_bytes(decrypted_config[5:7], byteorder='little')
174
+ reexecution_delay = int.from_bytes(decrypted_config[5:7], byteorder="little")
161
175
 
162
176
  c2_url = extract_c2_url(decrypted_config)
163
177
  if not c2_url:
164
178
  continue
165
- config_dict = {
166
- "Reexecution_delay": reexecution_delay,
167
- "C2": [c2_url]
168
- }
179
+ config_dict = {"raw": {"Reexecution_delay": reexecution_delay}, "CNCs": [c2_url]}
169
180
  return config_dict
170
181
  except Exception:
171
182
  continue
@@ -177,4 +188,3 @@ if __name__ == "__main__":
177
188
  with open(sys.argv[1], "rb") as f:
178
189
  config_json = json.dumps(extract_config(f.read()), indent=4)
179
190
  print(config_json)
180
-
@@ -37,7 +37,7 @@ def rc4_decrypt(key, ciphertext):
37
37
 
38
38
 
39
39
  def swap32(x):
40
- return int.from_bytes(x.to_bytes(4, byteorder='little'), byteorder='big', signed=False)
40
+ return int.from_bytes(x.to_bytes(4, byteorder="little"), byteorder="big", signed=False)
41
41
 
42
42
 
43
43
  def decode(buffer):
@@ -104,7 +104,7 @@ def extract_config(filebuf):
104
104
  break
105
105
  c2list_offset += delta
106
106
  if c2list != []:
107
- cfg["C2s"] = sorted(list(set(c2list)))
107
+ cfg["CNCs"] = sorted(list(set(c2list)))
108
108
  return cfg
109
109
 
110
110
 
@@ -11,15 +11,15 @@ def _is_ip(ip):
11
11
 
12
12
 
13
13
  def extract_config(data):
14
- config_dict = {"C2s": []}
14
+ config_dict = {}
15
15
  with suppress(Exception):
16
16
  if data[:2] == b"MZ":
17
17
  return
18
18
  for line in data.decode().split("\n"):
19
- if _is_ip(line) and line not in config_dict["C2s"]:
20
- config_dict["C2s"].append(line)
19
+ if _is_ip(line) and line not in config_dict.get("CNCs", []):
20
+ config_dict["CNCs"].append(line)
21
21
  elif line and "\\" in line:
22
22
  config_dict.setdefault("Timestamp path", []).append(line)
23
- elif "." in line and "=" not in line and line not in config_dict["C2s"]:
24
- config_dict.setdefault("Dummy domain", []).append(line)
23
+ elif "." in line and "=" not in line and line not in config_dict["CNCs"]:
24
+ config_dict.setdefault("raw", {}).setdefault("Dummy domain", []).append(line)
25
25
  return config_dict
@@ -68,9 +68,9 @@ def extract_config(data):
68
68
  try:
69
69
  decrypted = xor_data(line, chunks[i + 1]).decode()
70
70
  if "\r\n" in decrypted and "|" not in decrypted:
71
- config["IP Blocklist"] = list(filter(None, decrypted.split("\r\n")))
71
+ config.setdefault("raw", {})["IP Blocklist"] = list(filter(None, decrypted.split("\r\n")))
72
72
  elif "|" in decrypted and "." in decrypted and "\r\n" not in decrypted:
73
- config["URLs"] = list(filter(None, decrypted.split("|")))
73
+ config["CNCs"] = list(filter(None, decrypted.split("|")))
74
74
  except Exception:
75
75
  continue
76
76
  matches = yara_rules.match(data=data)
@@ -84,5 +84,5 @@ def extract_config(data):
84
84
  c2key_offset = item.instances[0].offset
85
85
  key_rva = struct.unpack("i", data[c2key_offset + 28 : c2key_offset + 32])[0] - pe.OPTIONAL_HEADER.ImageBase
86
86
  key_offset = pe.get_offset_from_rva(key_rva)
87
- config["C2 key"] = string_from_offset(data, key_offset).decode()
87
+ config["cryptokey"] = string_from_offset(data, key_offset).decode()
88
88
  return config
@@ -72,6 +72,6 @@ def extract_config(data):
72
72
  configdata = unzip_config(data)
73
73
 
74
74
  if configdata:
75
- raw_config["config"] = decode(configdata)
75
+ raw_config["raw"] = decode(configdata)
76
76
 
77
77
  return raw_config
@@ -92,7 +92,8 @@ def extract_config(data):
92
92
  c2_host = dtxt[offset : offset + c2_size].decode("utf-16")
93
93
  offset += c2_size
94
94
  c2_port = struct.unpack("H", dtxt[offset : offset + 2])[0]
95
- cfg["C2"] = f"{c2_host}:{c2_port}"
95
+ # ToDo missed schema
96
+ cfg["CNCs"] = [f"{c2_host}:{c2_port}"]
96
97
  offset += 2
97
98
  # unk1 = dtxt[offset : offset + 7]
98
99
  offset += 7
@@ -104,7 +105,7 @@ def extract_config(data):
104
105
  offset += 2
105
106
  runkey_size = struct.unpack("i", dtxt[offset : offset + 4])[0]
106
107
  offset += 4
107
- cfg["Run Key Name"] = dtxt[offset : offset + runkey_size].decode("utf-16")
108
+ cfg.setdefault("raw", {})["Run Key Name"] = dtxt[offset : offset + runkey_size].decode("utf-16")
108
109
  except struct.error:
109
110
  # there is a lot of failed data validation muting it
110
111
  return
@@ -76,8 +76,8 @@ def string_from_offset(data, offset):
76
76
 
77
77
  def parse_config(data):
78
78
  parsed = {}
79
- parsed["Botnet Id"] = data[4:].split(b"\x00", 1)[0].decode("utf-8")
80
- parsed["Campaign Id"] = data[25:].split(b"\x00", 1)[0].decode("utf-8")
79
+ parsed["botnet"] = data[4:].split(b"\x00", 1)[0].decode("utf-8")
80
+ parsed["campaign"] = data[25:].split(b"\x00", 1)[0].decode("utf-8")
81
81
  c2s = []
82
82
  c2_data = data[46:686]
83
83
  for i in range(10):
@@ -85,16 +85,17 @@ def parse_config(data):
85
85
  chunk = chunk.rstrip(b"\x00")
86
86
  if chunk:
87
87
  c2s.append(chunk.decode("utf-8"))
88
- parsed["C2s"] = c2s
89
- parsed["Public Key"] = data[704:].split(b"\x00", 1)[0]
88
+ parsed["CNCs"] = c2s
89
+ parsed["cryptokey"] = data[704:].split(b"\x00", 1)[0]
90
+ parsed["cryptokey_type"] = "RSA Public Key"
90
91
  dns_data = data[1004:].split(b"\x00", 1)[0]
91
92
  parsed["TLS SNI"] = dns_data.split(b"~")[0].decode("utf-8").rstrip()
92
93
  parsed["DNS C2"] = dns_data.split(b"~")[1].decode("utf-8").strip()
93
94
  dns_ips = []
94
- dns_ips.append(socket.inet_ntoa(data[1204:1208].split(b"\x00",1)[0]))
95
+ dns_ips.append(socket.inet_ntoa(data[1204:1208].split(b"\x00", 1)[0]))
95
96
  dns_ip_data = data[1208:1248]
96
97
  for i in range(10):
97
- chunk = dns_ip_data[i * 4:(i + 1) * 4]
98
+ chunk = dns_ip_data[i * 4 : (i + 1) * 4]
98
99
  chunk = chunk.rstrip(b"\x00")
99
100
  if chunk:
100
101
  dns_ips.append(socket.inet_ntoa(chunk))
@@ -103,6 +104,7 @@ def parse_config(data):
103
104
 
104
105
 
105
106
  def extract_config(filebuf):
107
+ config = {}
106
108
  end_config = {}
107
109
  pe = pefile.PE(data=filebuf, fast_load=False)
108
110
  image_base = pe.OPTIONAL_HEADER.ImageBase
@@ -167,14 +169,15 @@ def extract_config(filebuf):
167
169
  enc_data = filebuf[data_offset:].split(b"\0\0", 1)[0]
168
170
  raw = decrypt_rc4(key, enc_data)
169
171
  items = list(filter(None, raw.split(b"\x00\x00")))
170
- end_config["Botnet name"] = items[1].lstrip(b"\x00")
171
- end_config["Campaign ID"] = items[2]
172
+ config["botnet"] = items[1].lstrip(b"\x00")
173
+ config["campaign"] = items[2]
172
174
  for item in items:
173
175
  item = item.lstrip(b"\x00")
174
176
  if item.startswith(b"http"):
175
- end_config.setdefault("address", []).append(item)
177
+ config.setdefault("CNCs", []).append(item)
176
178
  elif len(item) == 16:
177
- end_config["RC4 key"] = item
179
+ config["cryptokey"] = item
180
+ config["cryptokey_type"] = "RC4"
178
181
  elif conf_type == "2" and decrypt_key:
179
182
  conf_va = struct.unpack("I", filebuf[decrypt_conf + cva : decrypt_conf + cva + 4])[0]
180
183
  conf_offset = pe.get_offset_from_rva(conf_va + pe.get_rva_from_offset(decrypt_conf) + cva + 4)
@@ -186,14 +189,15 @@ def extract_config(filebuf):
186
189
  conf_data = filebuf[conf_offset : conf_offset + conf_size]
187
190
  raw = decrypt_rc4(key, conf_data)
188
191
  items = list(filter(None, raw.split(b"\x00\x00")))
189
- end_config["Botnet name"] = items[0].decode("utf-8")
190
- end_config["Campaign ID"] = items[1].decode("utf-8")
192
+ config["botnet"] = items[0].decode("utf-8")
193
+ config["campaign"] = items[1].decode("utf-8")
191
194
  for item in items:
192
195
  item = item.lstrip(b"\x00")
193
196
  if item.startswith(b"http"):
194
- end_config.setdefault("address", []).append(item.decode("utf-8"))
197
+ config.setdefault("CNCs", []).append(item.decode("utf-8"))
195
198
  elif b"PUBLIC KEY" in item:
196
- end_config["Public key"] = item.decode("utf-8").replace("\n", "")
199
+ config["cryptokey"] = item.decode("utf-8").replace("\n", "")
200
+ config["cryptokey_type"] = "RSA Public key"
197
201
  elif conf_type == "3" and rc4_chunk1 and rc4_chunk2:
198
202
  conf_va = struct.unpack("I", filebuf[decrypt_conf : decrypt_conf + 4])[0]
199
203
  conf_offset = pe.get_offset_from_rva(conf_va + pe.get_rva_from_offset(decrypt_conf) + 4)
@@ -208,8 +212,10 @@ def extract_config(filebuf):
208
212
  conf = decrypt_rc4(decrypt_key, conf_data)
209
213
  end_config = parse_config(conf)
210
214
 
211
- return end_config
215
+ if config and end_config:
216
+ config = config.update({"raw": end_config})
212
217
 
218
+ return config
213
219
 
214
220
  if __name__ == "__main__":
215
221
  import sys
@@ -5,6 +5,7 @@ HAVE_MLW_CONFIGS = False
5
5
  with suppress(ImportError):
6
6
  # We do not install this by default as is outdated now, but if installed will be imported
7
7
  from malwareconfig.common import Decoder
8
+
8
9
  HAVE_MLW_CONFIGS = True
9
10
 
10
11
 
cape_parsers/__init__.py CHANGED
@@ -12,10 +12,11 @@ from typing import Dict, Tuple
12
12
  PARSERS_ROOT = Path(__file__).absolute().parent
13
13
  log = logging.getLogger()
14
14
 
15
- def load_cape_parsers(load: str="all", exclude_parsers: list = []):
15
+
16
+ def load_cape_parsers(load: str = "all", exclude_parsers: list = []):
16
17
  """
17
- load: all, core, community
18
- exclude_parsers: [names of parsers that will be ignored]
18
+ load: all, core, community
19
+ exclude_parsers: [names of parsers that will be ignored]
19
20
  """
20
21
  versions = {
21
22
  "cape": "core",
@@ -25,7 +26,9 @@ def load_cape_parsers(load: str="all", exclude_parsers: list = []):
25
26
  cape_parsers = {}
26
27
  CAPE_DECODERS = {
27
28
  "cape": [os.path.basename(decoder)[:-3] for decoder in glob.glob(os.path.join(PARSERS_ROOT, "CAPE", "core", "[!_]*.py"))],
28
- "community": [os.path.basename(decoder)[:-3] for decoder in glob.glob(os.path.join(PARSERS_ROOT, "CAPE", "community", "[!_]*.py"))],
29
+ "community": [
30
+ os.path.basename(decoder)[:-3] for decoder in glob.glob(os.path.join(PARSERS_ROOT, "CAPE", "community", "[!_]*.py"))
31
+ ],
29
32
  }
30
33
 
31
34
  for version, names in CAPE_DECODERS.items():
@@ -54,6 +57,7 @@ def load_mwcp_parsers() -> Tuple[Dict[str, str], ModuleType]:
54
57
  with suppress(ImportError):
55
58
  # We do not install this by default
56
59
  import mwcp
60
+
57
61
  HAVE_MWCP = True
58
62
 
59
63
  if not HAVE_MWCP:
@@ -66,6 +70,7 @@ def load_mwcp_parsers() -> Tuple[Dict[str, str], ModuleType]:
66
70
  return {}, mwcp
67
71
  return _malware_parsers, mwcp
68
72
 
73
+
69
74
  def _malduck_load_decoders():
70
75
 
71
76
  malduck_modules = {}
@@ -80,6 +85,7 @@ def _malduck_load_decoders():
80
85
 
81
86
  return malduck_modules
82
87
 
88
+
83
89
  """
84
90
  def load_malduck_parsers():
85
91
  HAVE_MALDUCK = False
@@ -108,6 +114,7 @@ def load_malduck_parsers():
108
114
  return malduck_modules
109
115
  """
110
116
 
117
+
111
118
  def load_malwareconfig_parsers() -> Tuple[bool, dict, ModuleType]:
112
119
  try:
113
120
  from malwareconfig import fileparser
@@ -125,12 +132,14 @@ def load_malwareconfig_parsers() -> Tuple[bool, dict, ModuleType]:
125
132
  log.error(e, exc_info=True)
126
133
  return False, False, False
127
134
 
135
+
128
136
  def load_ratdecoders_parsers():
129
137
  dec_modules = {}
130
138
  HAVE_MLW_CONFIGS = False
131
139
  with suppress(ImportError):
132
140
  # We do not install this by default as is outdated now, but if installed will be imported
133
141
  from malwareconfig.common import Decoder
142
+
134
143
  HAVE_MLW_CONFIGS = True
135
144
 
136
145
  if not HAVE_MLW_CONFIGS:
@@ -0,0 +1,59 @@
1
+ import pefile
2
+
3
+
4
+ def extract_raw_config(raw_data):
5
+ try:
6
+ pe = pefile.PE(data=raw_data)
7
+ rt_string_idx = [entry.id for entry in pe.DIRECTORY_ENTRY_RESOURCE.entries].index(pefile.RESOURCE_TYPE["RT_RCDATA"])
8
+ rt_string_directory = pe.DIRECTORY_ENTRY_RESOURCE.entries[rt_string_idx]
9
+ for entry in rt_string_directory.directory.entries:
10
+ if str(entry.name) == "SETTINGS":
11
+ data_rva = entry.directory.entries[0].data.struct.OffsetToData
12
+ size = entry.directory.entries[0].data.struct.Size
13
+ data = pe.get_memory_mapped_image()[data_rva : data_rva + size]
14
+ return data.split("}")
15
+ except Exception:
16
+ return None
17
+
18
+
19
+ def decode(line):
20
+ return "".join(chr(ord(char) - 1) for char in line)
21
+
22
+
23
+ def domain_parse(config):
24
+ return [domain.split(":", 1)[0] for domain in config["Domains"].split(";")]
25
+
26
+
27
+ def extract_config(data):
28
+ try:
29
+ config_raw = extract_raw_config(data)
30
+ if config_raw:
31
+ return {
32
+ "mutex": decode(config_raw[1])[::-1],
33
+ "raw": {
34
+ "Anti Sandboxie": decode(config_raw[2])[::-1],
35
+ "Max Folder Size": decode(config_raw[3])[::-1],
36
+ "Delay Time": decode(config_raw[4])[::-1],
37
+ "Password": decode(config_raw[5])[::-1],
38
+ "Kernel Mode Unhooking": decode(config_raw[6])[::-1],
39
+ "User More Unhooking": decode(config_raw[7])[::-1],
40
+ "Melt Server": decode(config_raw[8])[::-1],
41
+ "Offline Screen Capture": decode(config_raw[9])[::-1],
42
+ "Offline Keylogger": decode(config_raw[10])[::-1],
43
+ "Copy To ADS": decode(config_raw[11])[::-1],
44
+ "Domain": decode(config_raw[12])[::-1],
45
+ "Persistence Thread": decode(config_raw[13])[::-1],
46
+ "Active X Key": decode(config_raw[14])[::-1],
47
+ "Registry Key": decode(config_raw[15])[::-1],
48
+ "Active X Run": decode(config_raw[16])[::-1],
49
+ "Registry Run": decode(config_raw[17])[::-1],
50
+ "Safe Mode Startup": decode(config_raw[18])[::-1],
51
+ "Inject winlogon.exe": decode(config_raw[19])[::-1],
52
+ "Install Name": decode(config_raw[20])[::-1],
53
+ "Install Path": decode(config_raw[21])[::-1],
54
+ "Campaign Name": decode(config_raw[22])[::-1],
55
+ "Campaign Group": decode(config_raw[23])[::-1],
56
+ }
57
+ }
58
+ except Exception:
59
+ return None
@@ -35,5 +35,5 @@ def extract_config(filebuf):
35
35
  with suppress(Exception):
36
36
  dec = decrypt_string(item.lstrip(b"\x00").rstrip(b"\x00").decode())
37
37
  if "dll" not in dec and " " not in dec and ";" not in dec and "." in dec:
38
- cfg.setdefault("address", []).append(dec)
38
+ cfg.setdefault("CNCs", []).append(dec)
39
39
  return cfg
@@ -55,7 +55,7 @@ def string_from_offset(data, offset):
55
55
 
56
56
 
57
57
  def extract_config(filebuf):
58
- tmp_config = {}
58
+ config = {}
59
59
  yara_matches = yara_scan(filebuf)
60
60
 
61
61
  c2_offsets = []
@@ -70,6 +70,6 @@ def extract_config(filebuf):
70
70
  for c2_offset in c2_offsets:
71
71
  c2_url = string_from_offset(filebuf, c2_offset)
72
72
  if c2_url:
73
- tmp_config.setdefault("c2_url", []).append(c2_url)
73
+ config.setdefault("CNCs", []).append(c2_url)
74
74
 
75
- return tmp_config
75
+ return config
@@ -64,7 +64,7 @@ def extract_config(filebuf):
64
64
 
65
65
  c2_address = string_from_offset(filebuf, yara_offset + 0x2E8)
66
66
  if c2_address:
67
- return_conf["c2_address"] = c2_address
67
+ return_conf["CNCs"] = c2_address
68
68
 
69
69
  c2_url = string_from_offset(filebuf, yara_offset + 0xE8)
70
70
  if c2_url:
@@ -16,9 +16,7 @@ DESCRIPTION = "EvilGrab configuration parser."
16
16
  AUTHOR = "kevoreilly"
17
17
 
18
18
  import struct
19
-
20
19
  import pefile
21
-
22
20
  import yara
23
21
 
24
22
  rule_source = """
@@ -88,21 +86,22 @@ def extract_config(filebuf):
88
86
 
89
87
  yara_offset = int(yara_matches[key])
90
88
 
89
+ # ToDo missed schema
91
90
  c2_address = string_from_va(pe, yara_offset + values[0])
92
91
  if c2_address:
93
- end_config["c2_address"] = c2_address
92
+ end_config["CNCs"] = c2_address
94
93
  port = str(struct.unpack("h", filebuf[yara_offset + values[1] : yara_offset + values[1] + 2])[0])
95
94
  if port:
96
- end_config["port"] = [port, "tcp"]
95
+ end_config.setdefault("raw", {})["port"] = [port, "tcp"]
97
96
  missionid = string_from_va(pe, yara_offset + values[3])
98
97
  if missionid:
99
- end_config["missionid"] = missionid
98
+ end_config.setdefault("raw", {})["missionid"] = missionid
100
99
  version = string_from_va(pe, yara_offset + values[4])
101
100
  if version:
102
101
  end_config["version"] = version
103
102
  injectionprocess = string_from_va(pe, yara_offset + values[5])
104
103
  if injectionprocess:
105
- end_config["injectionprocess"] = injectionprocess
104
+ end_config.setdefault("raw", {})["injectionprocess"] = injectionprocess
106
105
  if key != "$configure3":
107
106
  mutex = string_from_va(pe, yara_offset - values[6])
108
107
  if mutex:
@@ -87,4 +87,6 @@ def parse_config(raw_config):
87
87
  def extract_config(data):
88
88
  raw_config = get_config(data)
89
89
  if raw_config:
90
- return parse_config(raw_config)
90
+ config = parse_config(raw_config)
91
+ if config:
92
+ return {"raw": config}
@@ -17,9 +17,7 @@ AUTHOR = "kevoreilly"
17
17
 
18
18
 
19
19
  import struct
20
-
21
20
  import pefile
22
-
23
21
  import yara
24
22
 
25
23
  rule_source = """
@@ -91,6 +89,7 @@ def extract_config(filebuf):
91
89
  # image_base = pe.OPTIONAL_HEADER.ImageBase
92
90
 
93
91
  yara_matches = yara_scan(filebuf)
92
+ config = {}
94
93
  tmp_config = {}
95
94
  for key, values in match_map.keys():
96
95
  if yara_matches.get(key):
@@ -103,23 +102,23 @@ def extract_config(filebuf):
103
102
 
104
103
  c2_address = unicode_from_va(pe, yara_offset + values[1])
105
104
  if c2_address:
106
- tmp_config.setdefault("c2_address", []).append(c2_address)
105
+ config.setdefault("CNCs", []).append(c2_address)
107
106
 
108
107
  if key == "$connect_3":
109
108
  c2_address = unicode_from_va(pe, yara_offset + values[2])
110
109
  if c2_address:
111
- tmp_config.setdefault("c2_address", []).append(c2_address)
110
+ config.setdefault("CNCs", []).append(c2_address)
112
111
  else:
113
112
  c2_address = unicode_from_va(pe, yara_offset + values[0])
114
113
  if c2_address:
115
- tmp_config["c2_address"] = c2_address
114
+ config["CNCs"] = [c2_address]
116
115
 
117
116
  filepath = unicode_from_va(pe, yara_offset + values[1])
118
117
  if filepath:
119
- tmp_config["filepath"] = filepath
118
+ config["filepath"] = filepath
120
119
 
121
120
  injectionprocess = unicode_from_va(pe, yara_offset - values[2])
122
121
  if injectionprocess:
123
- tmp_config["injectionprocess"] = injectionprocess
122
+ config["injectionprocess"] = injectionprocess
124
123
 
125
- return tmp_config
124
+ return {"raw": tmp_config}.update(config)
@@ -74,4 +74,6 @@ def extract_config(data):
74
74
  clean_config = version_21(raw_config)
75
75
  elif len(raw_config) == 20:
76
76
  clean_config = version_22(raw_config)
77
+ if clean_config:
78
+ clean_config = {"raw": clean_config}
77
79
  return clean_config
@@ -32,4 +32,5 @@ def extract_config(data):
32
32
  config_dict["Install Path"] = "TEMP"
33
33
  if config_parts[19] == "True":
34
34
  config_dict["Install Path"] = "Documents"
35
- return config_dict
35
+ if config_dict:
36
+ return {"raw": config_dict}
@@ -16,9 +16,7 @@ DESCRIPTION = "RCSession configuration parser."
16
16
  AUTHOR = "kevoreilly"
17
17
 
18
18
  import struct
19
-
20
19
  import pefile
21
-
22
20
  import yara
23
21
 
24
22
  rule_source = """
@@ -99,24 +97,24 @@ def extract_config(filebuf):
99
97
 
100
98
  c2_address = str(tmp_config[156 : 156 + MAX_IP_STRING_SIZE])
101
99
  if c2_address:
102
- end_config.setdefault("c2_address", []).append(c2_address)
100
+ end_config.setdefault("CNCs", []).append(c2_address)
103
101
  c2_address = str(tmp_config[224 : 224 + MAX_IP_STRING_SIZE])
104
102
  if c2_address:
105
- end_config.setdefault("c2_address", []).append(c2_address)
103
+ end_config.setdefault("CNCs", []).append(c2_address)
106
104
  installdir = unicode_string_from_offset(bytes(tmp_config), 0x2A8, 128)
107
105
  if installdir:
108
- end_config["directory"] = installdir
106
+ end_config.setdefault("raw", {})["directory"] = installdir
109
107
  executable = unicode_string_from_offset(tmp_config, 0x4B0, 128)
110
108
  if executable:
111
- end_config["filename"] = executable
109
+ end_config.setdefault("raw", {})["filename"] = executable
112
110
  servicename = unicode_string_from_offset(tmp_config, 0x530, 128)
113
111
  if servicename:
114
- end_config["servicename"] = servicename
112
+ end_config.setdefault("raw", {})["servicename"] = servicename
115
113
  displayname = unicode_string_from_offset(tmp_config, 0x738, 128)
116
114
  if displayname:
117
- end_config["servicedisplayname"] = displayname
115
+ end_config.setdefault("raw", {})["servicedisplayname"] = displayname
118
116
  description = unicode_string_from_offset(tmp_config, 0x940, 512)
119
117
  if description:
120
- end_config["servicedescription"] = description
118
+ end_config.setdefault("raw", {})["servicedescription"] = description
121
119
 
122
120
  return end_config