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
@@ -2,22 +2,35 @@ from contextlib import suppress
2
2
 
3
3
 
4
4
  def extract_config(data):
5
- config_dict = {}
5
+ config = {}
6
6
 
7
7
  with suppress(Exception):
8
8
  i = 0
9
9
  lines = data.decode().split("\n")
10
10
  for line in lines:
11
11
  if line.startswith("Mozilla"):
12
- config_dict["User Agent"] = line
13
- config_dict["C2"] = list(set(lines[i - 2].split(",")))
14
- config_dict["Port"] = lines[i - 1]
15
- config_dict["URI"] = lines[i + 3].split(",")
16
- config_dict["Keys"] = [lines[i + 1], lines[i + 2]]
12
+ cncs = list(set(lines[i - 2].split(",")))
13
+ port = lines[i - 1]
14
+ uris = lines[i + 3].split(",")
15
+ keys = [lines[i + 1], lines[i + 2]]
16
+
17
+ for cnc in cncs:
18
+ # ToDo need to verify if we have schema and uri has slash
19
+ for uri in uris:
20
+ config.setdefault("CNCs", []).append(f"{cnc}:{port}{uri}")
21
+
22
+ config["raw"] = {
23
+ "User Agent": line,
24
+ "C2": cncs,
25
+ "Port": port,
26
+ "URI": uri,
27
+ # ToDo move to proper field
28
+ "Keys": keys,
29
+ }
17
30
  break
18
31
  i += 1
19
32
 
20
- return config_dict
33
+ return config
21
34
 
22
35
 
23
36
  if __name__ == "__main__":
@@ -6,6 +6,7 @@ import traceback
6
6
  from contextlib import suppress
7
7
 
8
8
  import pefile
9
+
9
10
  # import regex as re
10
11
  # test
11
12
  import re
@@ -106,7 +107,7 @@ def extract_config_data(data, pe, config_match):
106
107
 
107
108
 
108
109
  def extract_2024(pe, filebuf):
109
- cfg = {}
110
+ config = {}
110
111
  rc4key_init_offset = 0
111
112
  botid_init_offset = 0
112
113
  port_init_offset = 0
@@ -142,45 +143,55 @@ def extract_2024(pe, filebuf):
142
143
  key_offset = pe.get_dword_from_offset(rc4key_init_offset + 57)
143
144
  key_rva = pe.get_rva_from_offset(rc4key_init_offset + 61) + key_offset
144
145
  key = pe.get_string_at_rva(key_rva)
145
- cfg["RC4 key"] = key.decode()
146
146
 
147
+ botid = ""
147
148
  botid_offset = pe.get_dword_from_offset(botid_init_offset + 51)
148
149
  botid_rva = pe.get_rva_from_offset(botid_init_offset + 55) + botid_offset
149
150
  botid_len_offset = pe.get_dword_from_offset(botidlgt_init_offset + 31)
150
151
  botid_data = pe.get_data(botid_rva)[:botid_len_offset]
151
152
  with suppress(Exception):
152
153
  botid = ARC4.new(key).decrypt(botid_data).split(b"\x00")[0].decode()
153
- cfg["Botid"] = botid
154
154
 
155
+ port = ""
155
156
  port_offset = pe.get_dword_from_offset(port_init_offset + 23)
156
157
  port_rva = pe.get_rva_from_offset(port_init_offset + 27) + port_offset
157
158
  port_len_offset = pe.get_dword_from_offset(botidlgt_init_offset + 4)
158
159
  port_data = pe.get_data(port_rva)[:port_len_offset]
159
160
  with suppress(Exception):
160
161
  port = ARC4.new(key).decrypt(port_data).split(b"\x00")[0].decode()
161
- cfg["Port"] = port
162
162
 
163
163
  dgaseed_offset = pe.get_dword_from_offset(dga1_init_offset + 15)
164
164
  dgaseed_rva = pe.get_rva_from_offset(dga1_init_offset + 19) + dgaseed_offset
165
165
  dgaseed_data = pe.get_qword_at_rva(dgaseed_rva)
166
- cfg["DGA seed"] = str(int(dgaseed_data))
167
166
 
168
167
  numdga_offset = pe.get_dword_from_offset(dga1_init_offset + 22)
169
168
  numdga_rva = pe.get_rva_from_offset(dga1_init_offset + 26) + numdga_offset
170
169
  numdga_data = pe.get_string_at_rva(numdga_rva)
171
- cfg["Number DGA domains"] = numdga_data.decode()
172
170
 
173
171
  domainlen_offset = pe.get_dword_from_offset(dga2_init_offset + 3)
174
172
  domainlen_rva = pe.get_rva_from_offset(dga2_init_offset + 7) + domainlen_offset
175
173
  domainlen_data = pe.get_string_at_rva(domainlen_rva)
176
- cfg["Domain length"] = domainlen_data.decode()
177
174
 
178
175
  tld_offset = pe.get_dword_from_offset(dga2_init_offset + 37)
179
176
  tld_rva = pe.get_rva_from_offset(dga2_init_offset + 41) + tld_offset
180
177
  tld_data = pe.get_string_at_rva(tld_rva).decode()
181
- cfg["TLD"] = tld_data
182
178
 
183
- return cfg
179
+ config = {
180
+ "dga_seed": str(int(dgaseed_data)),
181
+ "cryptokey": key.decode(),
182
+ "cryptokey_type": "RC4",
183
+ "raw": {
184
+ "TLD": tld_data,
185
+ "Domain length": domainlen_data.decode(),
186
+ "Number DGA domains": numdga_data.decode(),
187
+ },
188
+ }
189
+ if port:
190
+ config["raw"]["port"] = port
191
+ if botid:
192
+ config["botnet"] = botid
193
+
194
+ return config
184
195
 
185
196
 
186
197
  def extract_config(data):
@@ -209,27 +220,28 @@ def extract_config(data):
209
220
  if not key:
210
221
  continue
211
222
  if index == 0:
212
- cfg["Botnet ID"] = key.decode()
223
+ cfg["botnet"] = key.decode()
213
224
  elif index == 1:
214
- cfg["Campaign ID"] = key.decode()
225
+ cfg["campaign"] = key.decode()
215
226
  elif index == 2:
216
- cfg["Data"] = key.decode("latin-1")
227
+ cfg.setdefault("raw", {})["Data"] = key.decode("latin-1")
217
228
  elif index == 3:
218
- cfg["C2s"] = list(key.decode().split(","))
229
+ cfg["CNCs"] = list(key.decode().split(","))
219
230
  elif len(key_match) == 1:
220
231
  key = extract_key_data(data, pe, key_match[0])
221
232
  if not key:
222
233
  return cfg
223
- cfg["RC4 Key"] = key.decode()
234
+ cfg["cryptokey"] = key.decode()
235
+ cfg["cryptokey_type"] = "RC4"
224
236
  # Extract config ciphertext
225
237
  config_match = regex.search(data)
226
238
  campaign_id, botnet_id, c2s = extract_config_data(data, pe, config_match)
227
239
  if campaign_id:
228
- cfg["Campaign ID"] = ARC4.new(key).decrypt(campaign_id).split(b"\x00")[0].decode()
240
+ cfg["campaign"] = ARC4.new(key).decrypt(campaign_id).split(b"\x00")[0].decode()
229
241
  if botnet_id:
230
- cfg["Botnet ID"] = ARC4.new(key).decrypt(botnet_id).split(b"\x00")[0].decode()
242
+ cfg["botnet"] = ARC4.new(key).decrypt(botnet_id).split(b"\x00")[0].decode()
231
243
  if c2s:
232
- cfg["C2s"] = list(ARC4.new(key).decrypt(c2s).split(b"\x00")[0].decode().split(","))
244
+ cfg["CNCs"] = list(ARC4.new(key).decrypt(c2s).split(b"\x00")[0].decode().split(","))
233
245
  except Exception as e:
234
246
  log.error("This is broken: %s", str(e), exc_info=True)
235
247
 
@@ -70,7 +70,7 @@ def parse_config(data, conf_map):
70
70
  except KeyError:
71
71
  config[f"unknown_{k}"] = v
72
72
 
73
- return config
73
+ return {"raw": config}
74
74
 
75
75
 
76
76
  def decode(data):
@@ -81,7 +81,7 @@ def decode(data):
81
81
  strval = translate_string(strval.decode("utf-8"))
82
82
  decoded_str = base64.b64decode(strval)
83
83
  if decoded_str.startswith(b"http"):
84
- config["C2"] = [x for x in decoded_str.decode("utf-8").split("|") if x.strip() != ""]
84
+ config["CNCs"] = [x for x in decoded_str.decode("utf-8").split("|") if x.strip() != ""]
85
85
  elif b"1=Yes" in decoded_str or b"1=No" in decoded_str:
86
86
  config.update(parse_config(decoded_str, config_map_1))
87
87
  else:
@@ -102,7 +102,7 @@ def extract_config(data):
102
102
  config = {}
103
103
  for item in data.split(b"\r\n")[:-1]:
104
104
  if item.startswith(b"0="):
105
- config["C2"] = [x for x in item[2:].decode("utf-8").split("|") if x.strip() != ""]
105
+ config["CNCs"] = [x for x in item[2:].decode("utf-8").split("|") if x.strip() != ""]
106
106
  else:
107
107
  config.update(parse_config(item, config_map_2))
108
108
  return config
@@ -72,7 +72,9 @@ def extract_config(filebuf):
72
72
  for item in raw.split(b"\x00"):
73
73
  data = "".join(convert_char(c) for c in item)
74
74
  if len(data) == 406:
75
- config["RSA public key"] = data
75
+ config.setdefault("cryptokey", data)
76
+ # ToDO proper naming here
77
+ config.setdefault("raw", {})["cryptokey_type"] = "RSA public key"
76
78
  elif len(data) > 1 and "\\x" not in data:
77
- config["strings"] = data
79
+ config.setdefault("raw", {})["strings"] = data
78
80
  return config
@@ -132,7 +132,7 @@ def extract_config(filebuf):
132
132
  port = str(struct.unpack("H", filebuf[c2_offset + 4 : c2_offset + 6])[0])
133
133
 
134
134
  if c2_address and port:
135
- cfg.setdefault("address", []).append(f"{c2_address}:{port}")
135
+ cfg.setdefault("CNCs", []).append(f"{c2_address}:{port}")
136
136
 
137
137
  c2_offset += 6 + delta
138
138
 
@@ -155,7 +155,8 @@ def extract_config(filebuf):
155
155
  )
156
156
  for item in raw.split(b"\x00"):
157
157
  if len(item) == LEN_BLOB_KEY - 1:
158
- cfg["RC4 key"] = item.split(b";", 1)[0].decode()
158
+ cfg["cryptokey"] = item.split(b";", 1)[0].decode()
159
+ cfg["cryptokey_type"] = "RC4"
159
160
 
160
161
  if botnet_code:
161
162
  botnet_rva = struct.unpack("i", filebuf[botnet_code + 23 : botnet_code + 27])[0] - image_base
@@ -163,7 +164,7 @@ def extract_config(filebuf):
163
164
  with suppress(struct.error):
164
165
  botnet_offset = pe.get_offset_from_rva(botnet_rva)
165
166
  botnet_id = struct.unpack("H", filebuf[botnet_offset : botnet_offset + 2])[0]
166
- cfg["Botnet ID"] = str(botnet_id)
167
+ cfg["botnet"] = str(botnet_id)
167
168
 
168
169
  return cfg
169
170
 
@@ -12,11 +12,11 @@ def extract_config(data):
12
12
  i += 1
13
13
  elif "www." not in lines[0]:
14
14
  return
15
- config_dict["C2"] = lines[i]
15
+ config_dict["CNCs"] = lines[i]
16
16
  decoys = []
17
17
  i += 1
18
18
  while len(lines[i]) > 0:
19
19
  decoys.append(lines[i])
20
20
  i += 1
21
- config_dict["Decoys"] = decoys
21
+ config_dict.setdefault("raw", {})["Decoys"] = decoys
22
22
  return config_dict
@@ -1,7 +1,4 @@
1
- try:
2
- import re2 as re
3
- except ImportError:
4
- import re
1
+ import re
5
2
 
6
3
  url_regex = re.compile(rb"http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+")
7
4
 
@@ -10,7 +7,7 @@ def extract_config(data):
10
7
  try:
11
8
  urls = [url.lower().decode() for url in url_regex.findall(data)]
12
9
  if urls:
13
- return {"URLs": urls}
10
+ return {"CNCs": urls}
14
11
  except Exception as e:
15
12
  print(e)
16
13
 
@@ -77,12 +77,12 @@ def extract_config(filebuf):
77
77
  decrypted_data = ARC4.new(key).decrypt(enc_config)
78
78
  config = list(filter(None, decrypted_data.split(b"\x00")))
79
79
  return {
80
- "family": "IcedID",
81
80
  "version": str(struct.unpack("I", decrypted_data[4:8])[0]),
82
- "paths": [{"path": config[1].decode(), "usage": "other"}],
83
- "http": [{"uri": controller[1:].decode()} for controller in config[2:]],
84
- "other": {
85
- "Bot ID": str(struct.unpack("I", decrypted_data[:4])[0]),
81
+ "botnet": str(struct.unpack("I", decrypted_data[:4])[0]),
82
+ "raw": {
83
+ "family": "IcedID",
84
+ "paths": [{"path": config[1].decode(), "usage": "other"}],
85
+ "http": [{"uri": controller[1:].decode()} for controller in config[2:]],
86
86
  },
87
87
  }
88
88
  except Exception as e:
@@ -19,7 +19,7 @@ import pefile
19
19
 
20
20
 
21
21
  def extract_config(filebuf):
22
- cfg = {}
22
+ config = {}
23
23
  pe = None
24
24
  with suppress(Exception):
25
25
  pe = pefile.PE(data=filebuf, fast_load=False)
@@ -35,9 +35,9 @@ def extract_config(filebuf):
35
35
  if n > 32:
36
36
  break
37
37
  campaign, c2 = struct.unpack("I30s", bytes(dec))
38
- cfg["C2"] = c2.split(b"\00", 1)[0].decode()
39
- cfg["Campaign"] = campaign
40
- return cfg
38
+ config["CNCs"] = c2.split(b"\00", 1)[0].decode()
39
+ config["campaign"] = campaign
40
+ return config
41
41
 
42
42
 
43
43
  if __name__ == "__main__":
@@ -197,17 +197,20 @@ def extract_config(filebuf):
197
197
  str_vals.remove(item)
198
198
 
199
199
  cfg = {
200
- "C2": c2,
201
- "Group name": campaign,
202
- "Campaign ID": fnv_hash(campaign.encode()),
203
- "Version": version,
204
- "RC4 key": rc4_key,
205
- "Strings": str_vals,
200
+ "CNCs": c2,
201
+ "campaign": fnv_hash(campaign.encode()),
202
+ "version": version,
203
+ "cryptokey": rc4_key,
204
+ "cryptokey_type": "RC4",
205
+ "raw": {
206
+ "Strings": str_vals,
207
+ "Group name": campaign,
208
+ },
206
209
  }
207
210
  except Exception as e:
208
211
  log.error("Error: %s", e)
209
212
 
210
- if not cfg.get("C2", False) and not cfg.get("Group name", False):
213
+ if not cfg.get("C2", False) and not cfg.get("raw", {}).get("Group name", False):
211
214
  cfg = None
212
215
  return cfg
213
216
 
@@ -75,7 +75,7 @@ def yara_scan(raw_data):
75
75
 
76
76
  def extract_config(filebuf):
77
77
  yara_hit = yara_scan(filebuf)
78
- cfg = {}
78
+ config = {}
79
79
 
80
80
  for hit in yara_hit:
81
81
  if hit.rule == "Oyster":
@@ -121,14 +121,16 @@ def extract_config(filebuf):
121
121
  if c2_matches:
122
122
  c2.extend(c2_matches)
123
123
 
124
- cfg = {
125
- "C2": c2,
126
- "Dll Version": dll_version,
127
- "Strings": str_vals,
124
+ config = {
125
+ "CNCs": c2,
126
+ 'version': dll_version,
127
+ "raw": {
128
+ "Strings": str_vals,
129
+ },
128
130
  }
129
131
  except Exception as e:
130
132
  log.error("Error: %s", e)
131
- return cfg
133
+ return config
132
134
 
133
135
 
134
136
  if __name__ == "__main__":
@@ -105,13 +105,13 @@ def get_config(input_data):
105
105
  c2s = get_c2s(data, number_of_c2s)
106
106
 
107
107
  return {
108
- "Version": version,
109
- "Campaign Name": campaign_name,
110
- "Registry Key": registry_key,
111
- "User Agent": user_agent,
108
+ "version": version,
109
+ "campaign": campaign_name,
110
+ "raw": {"Registry Key": registry_key},
111
+ "user_agent": user_agent,
112
112
  # "request_headers": request_headers,
113
113
  # "api_cmds": api_cmds,
114
- "C2s": c2s,
114
+ "CNCs": c2s,
115
115
  }
116
116
 
117
117
 
@@ -152,7 +152,7 @@ def extract_config(filebuf):
152
152
  out = wide_finder(test_out_ptxt).decode("utf-16le")
153
153
  if out:
154
154
  url = get_url(out)
155
- return {"C2": [url], "PowerShell": out}
155
+ return {"CNCs": [url], "raw": {"PowerShell": out}}
156
156
 
157
157
  if data:
158
158
  yara_hit = yara_scan(filebuf)
@@ -322,4 +322,6 @@ def extract_config(cfg_blob):
322
322
  reg_list.append(reg_name)
323
323
  if reg_list:
324
324
  config_output.update({"Registry black list": reg_list})
325
- return config_output
325
+
326
+ if config_output:
327
+ return {"raw": config_output}
@@ -493,7 +493,8 @@ def extract_config(filebuf):
493
493
  config = parse_config(filebuf[: len(filebuf) - 20])
494
494
  for k, v in config.items():
495
495
  end_config.setdefault(k, v)
496
- return end_config
496
+ if end_config:
497
+ return {"raw": end_config}
497
498
 
498
499
 
499
500
  if __name__ == "__main__":
@@ -57,12 +57,7 @@ def extract_config(filebuf):
57
57
  encrypted_string = struct.unpack_from(data_format, data, offset)[0]
58
58
 
59
59
  with suppress(IndexError, UnicodeDecodeError, ValueError):
60
- decrypted_result = (
61
- ARC4.new(key)
62
- .decrypt(encrypted_string)
63
- .replace(b"\x00", b"")
64
- .decode("utf-8")
65
- )
60
+ decrypted_result = ARC4.new(key).decrypt(encrypted_string).replace(b"\x00", b"").decode("utf-8")
66
61
 
67
62
  if decrypted_result and all(32 <= ord(char) <= 127 for char in decrypted_result):
68
63
  if len(decrypted_result) > 2:
@@ -105,12 +100,13 @@ def extract_config(filebuf):
105
100
  campaign_found = True
106
101
 
107
102
  elif is_hex(item):
108
- cfg["RC4 Key"] = item
103
+ cfg["cryptokey"] = item
104
+ cfg["cryptokey_type"] = "RC4"
109
105
  if i == 1:
110
106
  campaign_found = True
111
107
 
112
108
  elif "Mozilla" in item:
113
- cfg["User-agent"] = item
109
+ cfg["user_agent"] = item
114
110
  if i == 1:
115
111
  campaign_found = True
116
112
 
@@ -119,13 +115,13 @@ def extract_config(filebuf):
119
115
  campaign_found = True
120
116
 
121
117
  if campaign_found:
122
- cfg["Campaign"] = campaign
118
+ cfg["campaign"] = campaign
123
119
 
124
120
  if c2s:
125
- cfg["C2"] = c2s
121
+ cfg["CNCs"] = c2s
126
122
 
127
123
  if mutexes:
128
- cfg["Mutex"] = list(set(mutexes))
124
+ cfg["mutex"] = list(set(mutexes))
129
125
 
130
126
  return cfg
131
127
 
@@ -167,11 +167,11 @@ def extract_config(data):
167
167
  if not c2 or "." not in c2:
168
168
  return
169
169
 
170
- config_dict = {"C2": c2, "Botnet": botnet, "Key": key}
170
+ config_dict = {"CNCs": c2, "botnet": botnet, "cryptokey": key}
171
171
  if "Authorization" in user_strings:
172
172
  base_location = user_strings.index("Authorization")
173
173
  if base_location:
174
- config_dict["Authorization"] = user_strings[base_location - 1]
174
+ config_dict.setdefault("raw", {})["Authorization"] = user_strings[base_location - 1]
175
175
  return config_dict
176
176
 
177
177
 
@@ -25,34 +25,34 @@ FLAG = {b"\x00": "Disable", b"\x01": "Enable"}
25
25
 
26
26
  # From JPCERT and Elastic Security Labs
27
27
  idx_list = {
28
- 0: "Host:Port:Password", # String containing "domain:port:enable_tls" separated by the "\x1e" characte
29
- 1: "Botnet", # Name of the botnet
30
- 2: "Connect interval", # Interval in second between connection attempt to C2
31
- 3: "Install flag", # Install REMCOS on the machine host
32
- 4: "Setup HKCU\\Run", # Enable setup of the persistence in the registry
33
- 5: "Setup HKLM\\Run", # Enable setup of the persistence in the registry
28
+ 0: "Host:Port:Password", # String containing "domain:port:enable_tls" separated by the "\x1e" characte
29
+ 1: "Botnet", # Name of the botnet
30
+ 2: "Connect interval", # Interval in second between connection attempt to C2
31
+ 3: "Install flag", # Install REMCOS on the machine host
32
+ 4: "Setup HKCU\\Run", # Enable setup of the persistence in the registry
33
+ 5: "Setup HKLM\\Run", # Enable setup of the persistence in the registry
34
34
  6: "Setup HKLM\\Explorer\\Run",
35
- 7: "Keylog file max size", # Maximum size of the keylogging data before rotation
36
- 8: "Setup HKLM\\Explorer\\Run", # Enable setup of the persistence in the registry
37
- 9: "Install parent directory", # Parent directory of the install folder. Integer mapped to an hardcoded path
38
- 10: "Install filename", # Name of the REMCOS binary once installed
35
+ 7: "Keylog file max size", # Maximum size of the keylogging data before rotation
36
+ 8: "Setup HKLM\\Explorer\\Run", # Enable setup of the persistence in the registry
37
+ 9: "Install parent directory", # Parent directory of the install folder. Integer mapped to an hardcoded path
38
+ 10: "Install filename", # Name of the REMCOS binary once installed
39
39
  11: "Startup value",
40
- 12: "Hide file", # Enable super hiding the install directory and binary as well as setting them to read only
41
- 13: "Process injection flag", # Enable running the malware injected in another process
42
- 14: "Mutex", # String used as the malware mutex and registry key
43
- 15: "Keylogger mode", # Set keylogging capability. Keylogging mode, 0 = disabled, 1 = keylogging everything, 2 = keylogging specific window(s)
44
- 16: "Keylogger parent directory", # Parent directory of the keylogging folder. Integer mapped to an hardcoded path
45
- 17: "Keylogger filename", # Filename of the keylogged data
46
- 18: "Keylog crypt", # Enable encryption RC4 of the keylogger data file
47
- 19: "Hide keylog file", # Enable super hiding of the keylogger data file
48
- 20: "Screenshot flag", # Enable screen recording capability
49
- 21: "Screenshot time", # The time interval in minute for capturing each screenshot
50
- 22: "Take Screenshot option", # Enable screen recording for specific window names
51
- 23: "Take screenshot title", # String containing window names separated by the ";” character
52
- 24: "Take screenshot time", #s The time interval in second for capturing each screenshot when a specific window name is found in the current foreground window title
53
- 25: "Screenshot parent directory", # Parent directory of the screenshot folder. Integer mapped to an hardcoded path
54
- 26: "Screenshot folder", # Name of the screenshot folder
55
- 27: "Screenshot crypt flag", # Enable encryption of screenshots
40
+ 12: "Hide file", # Enable super hiding the install directory and binary as well as setting them to read only
41
+ 13: "Process injection flag", # Enable running the malware injected in another process
42
+ 14: "Mutex", # String used as the malware mutex and registry key
43
+ 15: "Keylogger mode", # Set keylogging capability. Keylogging mode, 0 = disabled, 1 = keylogging everything, 2 = keylogging specific window(s)
44
+ 16: "Keylogger parent directory", # Parent directory of the keylogging folder. Integer mapped to an hardcoded path
45
+ 17: "Keylogger filename", # Filename of the keylogged data
46
+ 18: "Keylog crypt", # Enable encryption RC4 of the keylogger data file
47
+ 19: "Hide keylog file", # Enable super hiding of the keylogger data file
48
+ 20: "Screenshot flag", # Enable screen recording capability
49
+ 21: "Screenshot time", # The time interval in minute for capturing each screenshot
50
+ 22: "Take Screenshot option", # Enable screen recording for specific window names
51
+ 23: "Take screenshot title", # String containing window names separated by the ";" character
52
+ 24: "Take screenshot time", # s The time interval in second for capturing each screenshot when a specific window name is found in the current foreground window title
53
+ 25: "Screenshot parent directory", # Parent directory of the screenshot folder. Integer mapped to an hardcoded path
54
+ 26: "Screenshot folder", # Name of the screenshot folder
55
+ 27: "Screenshot crypt flag", # Enable encryption of screenshots
56
56
  28: "Mouse option",
57
57
  29: "Unknown29",
58
58
  30: "Delete file",
@@ -60,28 +60,28 @@ idx_list = {
60
60
  32: "Unknown32",
61
61
  33: "Unknown33",
62
62
  34: "Unknown34",
63
- 35: "Audio recording flag", # Enable audio recording capability
64
- 36: "Audio record time", # Duration in second of each audio recording
65
- 37: "Audio parent directory", # Parent directory of the audio recording folder. Integer mapped to an hardcoded path
66
- 38: "Audio folder", # Name of the audio recording folder
67
- 39: "Disable UAC flage", # Disable UAC in the registry
68
- 40: "Logging mode", # Set logging mode: 0 = disabled, 1 = minimized in tray, 2 = console logging
69
- 41: "Connect delay", # Delay in second before the first connection attempt to the C2
70
- 42: "Keylogger specific window names", # String containing window names separated by the ";” character
71
- 43: "Browser cleaning on startup flag", # Enable cleaning web browsers cookies and logins on REMCOS startup
72
- 44: "Browser cleaning only for the first run flag", # Enable web browsers cleaning only on the first run of Remcos
73
- 45: "Browser cleaning sleep time in minutes", # Sleep time in minute before cleaning the web browsers
74
- 46: "UAC bypass flag", # Enable UAC bypass capability
63
+ 35: "Audio recording flag", # Enable audio recording capability
64
+ 36: "Audio record time", # Duration in second of each audio recording
65
+ 37: "Audio parent directory", # Parent directory of the audio recording folder. Integer mapped to an hardcoded path
66
+ 38: "Audio folder", # Name of the audio recording folder
67
+ 39: "Disable UAC flage", # Disable UAC in the registry
68
+ 40: "Logging mode", # Set logging mode: 0 = disabled, 1 = minimized in tray, 2 = console logging
69
+ 41: "Connect delay", # Delay in second before the first connection attempt to the C2
70
+ 42: "Keylogger specific window names", # String containing window names separated by the ";"" character
71
+ 43: "Browser cleaning on startup flag", # Enable cleaning web browsers cookies and logins on REMCOS startup
72
+ 44: "Browser cleaning only for the first run flag", # Enable web browsers cleaning only on the first run of Remcos
73
+ 45: "Browser cleaning sleep time in minutes", # Sleep time in minute before cleaning the web browsers
74
+ 46: "UAC bypass flag", # Enable UAC bypass capability
75
75
  47: "Unkown47",
76
- 48: "Install directory", # Name of the install directory
77
- 49: "Keylogger root directory", # Name of the keylogger directory
78
- 50: "Watchdog flag", # Enable watchdog capability
76
+ 48: "Install directory", # Name of the install directory
77
+ 49: "Keylogger root directory", # Name of the keylogger directory
78
+ 50: "Watchdog flag", # Enable watchdog capability
79
79
  51: "Unknown51",
80
- 52: "License", # License serial
81
- 53: "Screenshot mouse drawing flag", # Enable drawing the mouse on each screenshot
82
- 54: "TLS raw certificate (base64)", # Certificate in raw format used with tls enabled C2 communication
83
- 55: "TLS key (base64)", # Key of the certificate
84
- 56: "TLS raw peer certificate (base64)", # C2 public certificate in raw format
80
+ 52: "License", # License serial
81
+ 53: "Screenshot mouse drawing flag", # Enable drawing the mouse on each screenshot
82
+ 54: "TLS raw certificate (base64)", # Certificate in raw format used with tls enabled C2 communication
83
+ 55: "TLS key (base64)", # Key of the certificate
84
+ 56: "TLS raw peer certificate (base64)", # C2 public certificate in raw format
85
85
  57: "TLS client private key (base64)",
86
86
  58: "TLS server certificate (base64)",
87
87
  59: "Unknown59",
@@ -107,7 +107,15 @@ setup_list = {
107
107
  8: "%ProgramData%",
108
108
  }
109
109
 
110
- utf_16_string_list = ["Keylogger specific window names", "Install filename", "Install directory", "Startup value", "Keylogger filename", "Take screenshot title", "Keylogger root directory"]
110
+ utf_16_string_list = [
111
+ "Keylogger specific window names",
112
+ "Install filename",
113
+ "Install directory",
114
+ "Startup value",
115
+ "Keylogger filename",
116
+ "Take screenshot title",
117
+ "Keylogger root directory",
118
+ ]
111
119
  logger = logging.getLogger(__name__)
112
120
 
113
121
 
@@ -170,7 +178,7 @@ def extract_config(filebuf):
170
178
  key = blob[1 : keylen + 1]
171
179
  decrypted_data = ARC4.new(key).decrypt(blob[keylen + 1 :])
172
180
  p_data = OrderedDict()
173
- p_data["Version"] = check_version(filebuf)
181
+ config["version"] = check_version(filebuf)
174
182
 
175
183
  configs = re.split(rb"\|\x1e\x1e\x1f\|", decrypted_data)
176
184
 
@@ -189,7 +197,7 @@ def extract_config(filebuf):
189
197
  # various separators have been observed
190
198
  separator = next((x for x in (b"|", b"\x1e", b"\xff\xff\xff\xff") if x in cont))
191
199
  host, port, password = cont.split(separator, 1)[0].split(b":")
192
- p_data["Control"] = f"tcp://{host.decode()}:{port.decode()}"
200
+ config["CNCs"] = [f"tcp://{host.decode()}:{port.decode()}"]
193
201
  p_data["Password"] = password.decode()
194
202
  else:
195
203
  p_data[idx_list[i]] = cont
@@ -200,7 +208,7 @@ def extract_config(filebuf):
200
208
  if isinstance(v, bytes):
201
209
  with suppress(Exception):
202
210
  v = v.decoed()
203
- config[k] = v
211
+ config.setdefault("raw", {})[k] = v
204
212
 
205
213
  except Exception as e:
206
214
  logger.error(f"Caught an exception: {e}")