CAPE-parsers 0.1.55__py3-none-any.whl → 0.1.57__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.
@@ -83,7 +83,13 @@ def extract_config(data: bytes):
83
83
  config["CNCs"] = lines[base + index + x]
84
84
  break
85
85
  if config or config_dict:
86
- return config.setdefault("raw", config_dict)
86
+ config.setdefault("raw", config_dict)
87
+
88
+ # If the data exfiltration is done through SMTP, then patch the extracted CNCs to include SMTP credentials
89
+ if config_dict.get("Protocol") == "SMTP":
90
+ config['CNCs'] = [f"smtp://{config_dict.get('Username')}:{config_dict.get('Password')}@{domain}:{config_dict.get('Port','587')}" for domain in config_dict.get('CNCs', [])]
91
+
92
+ return config
87
93
 
88
94
  if __name__ == "__main__":
89
95
  import sys
@@ -0,0 +1,186 @@
1
+ import base64
2
+ import json
3
+ import pefile
4
+ import yara
5
+ import struct
6
+ import re
7
+ import ipaddress
8
+ from contextlib import suppress
9
+
10
+
11
+ DESCRIPTION = "Amatera Stealer parser"
12
+ AUTHOR = "YungBinary"
13
+
14
+ RULE_SOURCE = """
15
+ rule AmateraDecrypt
16
+ {
17
+ meta:
18
+ author = "YungBinary"
19
+ description = "Find Amatera XOR key"
20
+ strings:
21
+ $decrypt = {
22
+ A1 ?? ?? ?? ?? // mov eax, dword ptr ds:szXorKey ; "852149723"
23
+ 89 45 ?? // mov dword ptr [ebp+xor_key], eax
24
+ 8B 0D ?? ?? ?? ?? // mov ecx, dword ptr ds:szXorKey+4 ; "49723"
25
+ 89 4D ?? // mov dword ptr [ebp+xor_key+4], ecx
26
+ 66 8B 15 ?? ?? ?? ?? // mov dx, word ptr ds:szXorKey+8 ; "3"
27
+ 66 89 55 ?? // mov word ptr [ebp+xor_key+8], dx
28
+ 8D 45 ?? // lea eax, [ebp+xor_key]
29
+ 50 // push eax
30
+ E8 // call
31
+ }
32
+ condition:
33
+ uint16(0) == 0x5A4D and $decrypt
34
+ }
35
+ """
36
+
37
+
38
+ RULE_SOURCE_AES_KEY = """
39
+ rule AmateraAESKey
40
+ {
41
+ meta:
42
+ author = "YungBinary"
43
+ description = "Find Amatera AES key"
44
+ strings:
45
+ $aes_key_on_stack = {
46
+ 83 EC 2C // sub esp, 2Ch
47
+ C6 45 D4 ?? // mov byte ptr [ebp-2Ch], ??
48
+ C6 45 D5 ?? // mov byte ptr [ebp-2Bh], ??
49
+ C6 45 D6 ?? // mov byte ptr [ebp-2Ah], ??
50
+ C6 45 D7 ?? // mov byte ptr [ebp-29h], ??
51
+ C6 45 D8 ?? // mov byte ptr [ebp-28h], ??
52
+ C6 45 D9 ?? // mov byte ptr [ebp-27h], ??
53
+ C6 45 DA ?? // mov byte ptr [ebp-26h], ??
54
+ C6 45 DB ?? // mov byte ptr [ebp-25h], ??
55
+ C6 45 DC ?? // mov byte ptr [ebp-24h], ??
56
+ C6 45 DD ?? // mov byte ptr [ebp-23h], ??
57
+ C6 45 DE ?? // mov byte ptr [ebp-22h], ??
58
+ C6 45 DF ?? // mov byte ptr [ebp-21h], ??
59
+ C6 45 E0 ?? // mov byte ptr [ebp-20h], ??
60
+ C6 45 E1 ?? // mov byte ptr [ebp-1Fh], ??
61
+ C6 45 E2 ?? // mov byte ptr [ebp-1Eh], ??
62
+ C6 45 E3 ?? // mov byte ptr [ebp-1Dh], ??
63
+ C6 45 E4 ?? // mov byte ptr [ebp-1Ch], ??
64
+ C6 45 E5 ?? // mov byte ptr [ebp-1Bh], ??
65
+ C6 45 E6 ?? // mov byte ptr [ebp-1Ah], ??
66
+ C6 45 E7 ?? // mov byte ptr [ebp-19h], ??
67
+ C6 45 E8 ?? // mov byte ptr [ebp-18h], ??
68
+ C6 45 E9 ?? // mov byte ptr [ebp-17h], ??
69
+ C6 45 EA ?? // mov byte ptr [ebp-16h], ??
70
+ C6 45 EB ?? // mov byte ptr [ebp-15h], ??
71
+ C6 45 EC ?? // mov byte ptr [ebp-14h], ??
72
+ C6 45 ED ?? // mov byte ptr [ebp-13h], ??
73
+ C6 45 EE ?? // mov byte ptr [ebp-12h], ??
74
+ C6 45 EF ?? // mov byte ptr [ebp-11h], ??
75
+ C6 45 F0 ?? // mov byte ptr [ebp-10h], ??
76
+ C6 45 F1 ?? // mov byte ptr [ebp-0Fh], ??
77
+ C6 45 F2 ?? // mov byte ptr [ebp-0Eh], ??
78
+ C6 45 F3 ?? // mov byte ptr [ebp-0Dh], ??
79
+ C7 45 F4 10 00 00 00 // mov dword ptr [ebp-0Ch], 10h
80
+ }
81
+ condition:
82
+ uint16(0) == 0x5A4D and $aes_key_on_stack
83
+ }
84
+ """
85
+
86
+ DOMAIN_REGEX = r'^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$'
87
+
88
+
89
+ def yara_scan(raw_data: bytes, rule_source: str):
90
+ yara_rules = yara.compile(source=rule_source)
91
+ matches = yara_rules.match(data=raw_data)
92
+
93
+ for match in matches:
94
+ for block in match.strings:
95
+ for instance in block.instances:
96
+ return instance.offset
97
+
98
+
99
+ def extract_base64_strings(data: bytes, minchars: int, maxchars: int):
100
+ """
101
+ Generator that returns ASCII formatted base64 strings
102
+ """
103
+ apat = b"([A-Za-z0-9+/=]{" + str(minchars).encode() + b"," + str(maxchars).encode() + b"})\x00"
104
+ for s in re.findall(apat, data):
105
+ yield s.decode()
106
+
107
+
108
+ def xor_data(data, key):
109
+ decoded = bytearray()
110
+ for i in range(len(data)):
111
+ decoded.append(key[i % len(key)] ^ data[i])
112
+ return decoded
113
+
114
+
115
+ def is_public_ip(ip):
116
+ try:
117
+ # This will raise a ValueError if the IP format is incorrect
118
+ ip_obj = ipaddress.ip_address(ip.decode())
119
+ if ip_obj.is_private:
120
+ return False
121
+ return True
122
+ except Exception:
123
+ return False
124
+
125
+
126
+ def is_valid_domain(data):
127
+ try:
128
+ if re.fullmatch(DOMAIN_REGEX, data.decode()):
129
+ return True
130
+ return False
131
+ except Exception:
132
+ return False
133
+
134
+
135
+ def extract_config(data):
136
+ """
137
+ Extract Amatera malware configuration.
138
+ """
139
+ config_dict = {}
140
+
141
+ with suppress(Exception):
142
+ pe = pefile.PE(data=data)
143
+ image_base = pe.OPTIONAL_HEADER.ImageBase
144
+
145
+ # Identify XOR key decryption routine and extract key
146
+ offset = yara_scan(data, RULE_SOURCE)
147
+ if not offset:
148
+ return config_dict
149
+ key_str_va = struct.unpack('i', data[offset + 1: offset + 5])[0]
150
+ key_str = pe.get_string_at_rva(key_str_va - image_base, max_length=20) + b'\x00'
151
+
152
+ # Extract AES 256 key
153
+ aes_key_offset = yara_scan(data, RULE_SOURCE_AES_KEY)
154
+ aes_key = bytearray()
155
+ if aes_key_offset:
156
+ aes_block = data[aes_key_offset : aes_key_offset + 131]
157
+ for i in range(0, len(aes_block) - 4, 4):
158
+ aes_key.append(aes_block[i+6])
159
+
160
+ # Handle each base64 string -> decode -> decrypt with XOR key
161
+ for b64_str in extract_base64_strings(data, 8, 20):
162
+ try:
163
+ decoded = base64.b64decode(b64_str, validate=True)
164
+ decrypted = xor_data(decoded, key_str)
165
+ if not is_public_ip(decrypted) and not is_valid_domain(decrypted):
166
+ continue
167
+
168
+ config_dict["CNCs"] = [f"https://{decrypted.decode()}"]
169
+
170
+ if aes_key:
171
+ config_dict["cryptokey"] = aes_key.hex()
172
+ config_dict["cryptokey_type"] = "AES"
173
+
174
+ return config_dict
175
+ except Exception:
176
+ continue
177
+
178
+ return config_dict
179
+
180
+
181
+ if __name__ == "__main__":
182
+ import sys
183
+
184
+ with open(sys.argv[1], "rb") as f:
185
+ config_json = json.dumps(extract_config(f.read()), indent=4)
186
+ print(config_json)
@@ -18,7 +18,6 @@ import re
18
18
  from contextlib import suppress
19
19
 
20
20
  import pefile
21
-
22
21
  import yara
23
22
 
24
23
  log = logging.getLogger(__name__)
@@ -169,7 +168,13 @@ def extract_config(filebuf):
169
168
  c2_dec = decode_string(items[10], sbox).decode("utf8")
170
169
  if "|" in c2_dec:
171
170
  c2_dec = c2_dec.split("|")
172
- cfg["CNCs"] = c2_dec
171
+ for c2 in c2_dec:
172
+ # Assign the proper scheme based on the port
173
+ if c2.endswith(':443'):
174
+ c2 = f"https://{c2}"
175
+ else:
176
+ c2 = f"http://{c2}"
177
+ cfg.setdefault("CNCs", []).append(c2)
173
178
  if float(cfg["version"]) < 1.7:
174
179
  cfg["campaign"] = decode_string(items[276], sbox).decode("utf8")
175
180
  else:
@@ -55,11 +55,11 @@ def extract_config(memdump_path, read=False):
55
55
  if gate_url.match(url):
56
56
  config.setdefault("CNCs", []).append(url.decode())
57
57
  elif exe_url.match(url) or dll_url.match(url):
58
- artifacts_raw["downloads"].append(url.decode())
58
+ artifacts_raw.setdefault("downloads", []).append(url.decode())
59
59
  except Exception as e:
60
60
  print(e, sys.exc_info(), "PONY")
61
- config["CNCs"] = list(set(config["controllers"]))
62
- config.setdefault("raw", {})["downloads"] = list(set(artifacts_raw["downloads"]))
61
+ if "downloads" in artifacts_raw:
62
+ config.setdefault("raw", {})["downloads"] = list(set(artifacts_raw["downloads"]))
63
63
  return config
64
64
 
65
65
 
@@ -119,7 +119,7 @@ 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
  cncs = find_c2(decoded_payload)
122
- if cncs and list(filter(cncs, None)):
122
+ if cncs:
123
123
  config["CNCs"] = cncs
124
124
 
125
125
  return config
@@ -188,7 +188,17 @@ def extract_config(filebuf):
188
188
  cncs.append(config_dict["raw"]["BackupConnectionHost"])
189
189
  if config_dict.get("raw", {}).get("ConnectionPort") and cncs:
190
190
  port = config_dict["raw"]["ConnectionPort"]
191
- config_dict["CNCs"] = [f"{cnc}:{port}" for cnc in cncs]
191
+ config_dict["CNCs"] = [f"tcp://{cnc}:{port}" for cnc in cncs]
192
+
193
+ if "Mutex" in config_dict.get("raw", {}):
194
+ config_dict["mutex"] = config_dict["raw"]["Mutex"]
195
+
196
+ if "Version" in config_dict.get("raw", {}):
197
+ config_dict["version"] = config_dict["raw"]["Version"]
198
+
199
+ if "DefaultGroup" in config_dict.get("raw", {}):
200
+ config_dict["campaign"] = config_dict["raw"]["DefaultGroup"]
201
+
192
202
  return config_dict
193
203
 
194
204
 
@@ -176,7 +176,7 @@ def extract_config(data):
176
176
  config = get_clean_config(config_dict)
177
177
  if config:
178
178
  if config.get("domain") and config.get("port"):
179
- conf["CNCs"] = [f"{config['domain']}:{config['port']}"]
179
+ conf["CNCs"] = [f"tcp://{config['domain']}:{config['port']}"]
180
180
 
181
181
  if config.get("campaign_id"):
182
182
  conf["campaign"] = config["campaign_id"]
@@ -38,7 +38,7 @@ def handle_plain(dotnet_file, c2_type, user_strings):
38
38
  if c2_type == "Telegram":
39
39
  token = dotnet_file.net.user_strings.get(user_strings_list[15]).value.__str__()
40
40
  chat_id = dotnet_file.net.user_strings.get(user_strings_list[16]).value.__str__()
41
- return {"raw": {"Type": "Telegram"}, "CNCs": f"https://api.telegram.org/bot{token}/sendMessage?chat_id={chat_id}"}
41
+ return {"raw": {"Type": "Telegram"}, "CNCs": [f"https://api.telegram.org/bot{token}/sendMessage?chat_id={chat_id}"]}
42
42
  elif c2_type == "SMTP":
43
43
  smtp_from = dotnet_file.net.user_strings.get(user_strings_list[7]).value.__str__()
44
44
  smtp_password = dotnet_file.net.user_strings.get(user_strings_list[8]).value.__str__()
@@ -106,7 +106,7 @@ def handle_encrypted(dotnet_file, data, c2_type, user_strings):
106
106
  if decrypted_strings:
107
107
  if c2_type == "Telegram":
108
108
  token, chat_id = decrypted_strings
109
- config_dict = {"raw": {"Type": "Telegram"}, "CNCs": f"https://api.telegram.org/bot{token}/sendMessage?chat_id={chat_id}"}
109
+ config_dict = {"raw": {"Type": "Telegram"}, "CNCs": [f"https://api.telegram.org/bot{token}/sendMessage?chat_id={chat_id}"]}
110
110
  elif c2_type == "SMTP":
111
111
  smtp_from, smtp_password, smtp_host, smtp_to, smtp_port = decrypted_strings
112
112
  config_dict = {
@@ -61,7 +61,11 @@ def extract_config(data):
61
61
  enc_data = f.read(data_len - 32)
62
62
  config = decrypt_config(enc_data, key, iv)
63
63
  if config:
64
- return {"raw": config}
64
+ output = {"raw": config}
65
+ output["CNCs"] = [
66
+ f"{'http' if not config['secure'] else 'https'}://{config['host']}:{config['port']}{config['path']}"
67
+ ]
68
+ return output
65
69
  except Exception as e:
66
70
  log.error("Configuration decryption failed: %s", e)
67
71
  return {}
@@ -3,9 +3,8 @@ Description: Winos 4.0 "OnlineModule" config parser
3
3
  Author: x.com/YungBinary
4
4
  """
5
5
 
6
- from contextlib import suppress
7
6
  import re
8
-
7
+ from contextlib import suppress
9
8
 
10
9
  CONFIG_KEY_MAP = {
11
10
  "dd": "execution_delay_seconds",
@@ -60,7 +59,11 @@ def extract_config(data: bytes) -> dict:
60
59
 
61
60
  final_config["CNCs"] = list(set(final_config["CNCs"]))
62
61
  # Extract campaign ID
63
- final_config["campaign_id"] = "default" if config_dict["fz"] == "\u9ed8\u8ba4" else config_dict["fz"]
62
+ final_config["campaign"] = "default" if config_dict["fz"] == "\u9ed8\u8ba4" else config_dict["fz"]
63
+
64
+ # Check if the version has been extracted
65
+ if "bb" in config_dict:
66
+ final_config["version"] = config_dict["bb"]
64
67
 
65
68
  # Map keys, e.g. dd -> execution_delay_seconds
66
69
  final_config["raw"] = {v: config_dict[k] for k, v in CONFIG_KEY_MAP.items() if k in config_dict}
@@ -59,7 +59,19 @@ def parse_http_config(rc4_key: bytes, data: bytes) -> dict:
59
59
  config["sleep_delay"] = read("<I")
60
60
  config["jitter_delay"] = read("<I")
61
61
 
62
- return {"raw": config}
62
+ output = {"raw": config}
63
+
64
+ # Map some fields to CAPE's output format, where possible
65
+ output['cryptokey'] = config['cryptokey']
66
+ output['cryptokey_type'] = config['cryptokey_type']
67
+ output['user_agent'] = config['user_agent']
68
+ output['CNCs'] = [f"{'https' if config['use_ssl'] else 'http'}://{server}:{ports[i]}{config['uri']}"
69
+ for i, server in enumerate(servers)]
70
+
71
+ # TODO: Does agent_type map to version or build?
72
+ # output['version'] = output['raw']['agent_type']
73
+
74
+ return output
63
75
 
64
76
 
65
77
  def extract_config(filebuf: bytes) -> dict:
@@ -15,8 +15,8 @@ from pathlib import Path
15
15
  from struct import pack, unpack
16
16
 
17
17
  import pefile
18
-
19
18
  import yara
19
+
20
20
  from cape_parsers.utils.lznt1 import lznt1
21
21
 
22
22
  log = logging.getLogger(__name__)
@@ -556,4 +556,7 @@ def extract_config(data):
556
556
  }
557
557
  }
558
558
 
559
+ config["cryptokey"] = config["raw"]["Rabbit key"]
560
+ config["cryptokey_type"] = "Rabbit"
561
+
559
562
  return config
@@ -92,11 +92,14 @@ def decode(data):
92
92
 
93
93
 
94
94
  def extract_config(data):
95
+ config_extracted = False
95
96
  with suppress(pefile.PEFormatError):
96
97
  pe = pefile.PE(data=data, fast_load=True)
97
98
  for section in pe.sections:
98
99
  if b"CODE" in section.Name:
99
- return decode(section.get_data())
100
+ config = decode(section.get_data())
101
+ config_extracted = True
102
+ break
100
103
 
101
104
  if b"1=Yes" in data or b"1=No" in data:
102
105
  config = {}
@@ -105,6 +108,32 @@ def extract_config(data):
105
108
  config["CNCs"] = [x for x in item[2:].decode("utf-8").split("|") if x.strip() != ""]
106
109
  else:
107
110
  config.update(parse_config(item, config_map_2))
111
+ config_extracted = True
112
+
113
+ if config_extracted:
114
+ # Map information to CAPE's format, if possible
115
+ if "c2_port" in config["raw"] and "CNCs" in config:
116
+ # We're making the assumpton the same port is used for all CNCs
117
+ config["CNCs"] = [f"{url}:{config['raw']['c2_port']}" for url in config["CNCs"]]
118
+
119
+ if "internal_mutex" in config["raw"]:
120
+ # Bring mutex to top level
121
+ config["mutex"] = config["raw"]["internal_mutex"]
122
+
123
+ if "crypto_key" in config["raw"]:
124
+ # Bring crypto_key to top level
125
+ config["cryptokey"] = config["raw"]["crypto_key"]
126
+
127
+ if "campaign_id" in config["raw"]:
128
+ # Bring campaign_id to top level
129
+ config["campaign"] = config["raw"]["campaign_id"]
130
+
131
+ if "xor_key" in config["raw"]:
132
+ # Bring xor_key to top level
133
+ config["cryptokey"] = config["raw"]["xor_key"]
134
+ config["cruptokey_type"] = "XOR"
135
+
136
+
108
137
  return config
109
138
 
110
139
  return ""
@@ -35,7 +35,8 @@ def extract_config(filebuf):
35
35
  if n > 32:
36
36
  break
37
37
  campaign, c2 = struct.unpack("I30s", bytes(dec))
38
- config["CNCs"] = c2.split(b"\00", 1)[0].decode()
38
+ c2 = c2.split(b"\00", 1)[0].decode()
39
+ config["CNCs"] = f"http://{c2}"
39
40
  config["campaign"] = campaign
40
41
  return config
41
42
 
@@ -154,6 +154,7 @@ def extract_config(filebuf):
154
154
  minor = int.from_bytes(data[18:19], byteorder="big")
155
155
  release = int.from_bytes(data[26:27], byteorder="big")
156
156
  version = f"{major}.{minor}.{release}"
157
+
157
158
  if "$key" in item.identifier:
158
159
  key = instance.matched_data[4::5]
159
160
  try:
@@ -161,6 +162,7 @@ def extract_config(filebuf):
161
162
  data_sections = [s for s in pe.sections if s.Name.find(b".data") != -1]
162
163
  if not data_sections:
163
164
  return
165
+
164
166
  data = data_sections[0].get_data()
165
167
  str_vals = []
166
168
  c2 = []
@@ -14,6 +14,7 @@
14
14
 
15
15
  import logging
16
16
  import struct
17
+ from typing import List
17
18
 
18
19
  import pefile
19
20
  import yara
@@ -32,8 +33,8 @@ rule NitroBunnyDownloader
32
33
  cape_type = "NitroBunnyDownloader Payload"
33
34
  hash = "960e59200ec0a4b5fb3b44e6da763f5fec4092997975140797d4eec491de411b"
34
35
  strings:
35
- $config1 = {E8 [3] 00 41 B8 ?? ?? 00 00 48 8D 15 [3] 00 48 89 C1 48 89 ?? E8 [3] 00}
36
- $config2 = {E8 [3] 00 48 8D 15 [3] 00 41 B8 ?? ?? 00 00 48 89 C1 48 89 ?? E8 [3] 00}
36
+ $config1 = {E8 [3] 00 41 B8 ?? ?? 00 00 48 8D 15 [3] 00 48 89 C1 4? 89 ?? E8 [3] 00}
37
+ $config2 = {E8 [3] 00 48 8D 15 [3] 00 41 B8 ?? ?? 00 00 48 89 C1 4? 89 ?? E8 [3] 00}
37
38
  $string1 = "X-Amz-User-Agent:" wide
38
39
  $string2 = "Amz-Security-Flag:" wide
39
40
  $string3 = "/cart" wide
@@ -87,6 +88,20 @@ def read_string_list(data, off, count):
87
88
  return items, off
88
89
 
89
90
 
91
+ def make_endpoints(cncs: List[str], port: int, uris: List[str]) -> List[str]:
92
+ endpoints = []
93
+ schema = {80: "http", 443: "https"}.get(port, "tcp")
94
+ for cnc in cncs:
95
+ base_url = f"{schema}://{cnc}"
96
+ if port not in (80, 443):
97
+ base_url += f":{port}"
98
+
99
+ for uri in uris:
100
+ endpoints.append(f"{base_url}/{uri.lstrip('/')}")
101
+
102
+ return endpoints
103
+
104
+
90
105
  def extract_config(filebuf):
91
106
  yara_hit = yara_scan(filebuf)
92
107
  if not yara_hit:
@@ -98,24 +113,20 @@ def extract_config(filebuf):
98
113
  config_offset = None
99
114
  rva_offset = None
100
115
 
116
+ CONFIG_OFFSETS = {
117
+ "$config1": (7, 14, 18),
118
+ "$config2": (14, 8, 12),
119
+ }
120
+
101
121
  for hit in yara_hit:
102
122
  if hit.rule != "NitroBunnyDownloader":
103
123
  continue
104
124
 
105
125
  for item in hit.strings:
106
- for instance in item.instances:
107
- if item.identifier == "$config1":
108
- config_code_offset = instance.offset
109
- config_size_offset = 7
110
- config_offset= 14
111
- rva_offset = 18
112
- break
113
- elif item.identifier == "$config2":
114
- config_code_offset = instance.offset
115
- config_size_offset = 14
116
- config_offset= 8
117
- rva_offset = 12
118
- break
126
+ if item.identifier in CONFIG_OFFSETS and item.instances:
127
+ config_code_offset = item.instances[0].offset
128
+ config_size_offset, config_offset, rva_offset = CONFIG_OFFSETS[item.identifier]
129
+ break
119
130
 
120
131
  if config_code_offset:
121
132
  break
@@ -126,33 +137,26 @@ def extract_config(filebuf):
126
137
  try:
127
138
  pe = pefile.PE(data=filebuf, fast_load=True)
128
139
  config_length = pe.get_dword_from_offset(config_code_offset + config_size_offset)
129
- config = pe.get_dword_from_offset(config_code_offset + config_offset)
140
+ config_data_offset = pe.get_dword_from_offset(config_code_offset + config_offset)
130
141
  rva = pe.get_rva_from_offset(config_code_offset + rva_offset)
131
- config_rva = rva + config
142
+ config_rva = rva + config_data_offset
132
143
  data = pe.get_data(config_rva, config_length)
133
144
  off = 0
134
- raw = cfg["raw"] = {}
145
+ cfg["CNCs"] = []
135
146
  port, off = read_dword(data, off)
136
147
  num, off = read_dword(data, off)
137
148
  cncs, off = read_string_list(data, off, num)
138
149
  num, off = read_qword(data, off)
139
- raw["user_agent"], off = read_utf16le_string(data, off, num)
150
+ cfg["user_agent"], off = read_utf16le_string(data, off, num)
140
151
  num, off = read_dword(data, off)
152
+ raw = cfg["raw"] = {}
141
153
  raw["http_header_items"], off = read_string_list(data, off, num)
142
154
  num, off = read_dword(data, off)
143
- raw["uri_list"], off = read_string_list(data, off, num)
155
+ uris, off = read_string_list(data, off, num)
144
156
  raw["unknown_1"], off = read_dword(data, off)
145
157
  raw["unknown_2"], off = read_dword(data, off)
146
-
147
158
  if cncs:
148
- cfg["CNCs"] = []
149
- schema = {80: "http", 443: "https"}.get(port, "tcp")
150
- for cnc in cncs:
151
- cnc = f"{schema}://{cnc}"
152
- if port not in (80, 443):
153
- cnc += f":{port}"
154
-
155
- cfg["CNCs"].append(cnc)
159
+ cfg["CNCs"] = make_endpoints(cncs, port, uris)
156
160
 
157
161
  except Exception as e:
158
162
  log.error("Error: %s", e)
@@ -12,21 +12,21 @@
12
12
  # You should have received a copy of the GNU General Public License
13
13
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
14
14
 
15
-
15
+ import ipaddress
16
16
  import logging
17
17
  import re
18
18
  import struct
19
19
  from contextlib import suppress
20
+ from typing import List
20
21
 
21
- import yara
22
22
  import pefile
23
+ import yara
23
24
 
24
25
  log = logging.getLogger(__name__)
25
26
 
26
27
  DESCRIPTION = "Oyster configuration parser."
27
28
  AUTHOR = "enzok"
28
- yara_rules = yara.compile(
29
- source="""rule Oyster
29
+ yara_rules = yara.compile(source="""rule Oyster
30
30
  {
31
31
  meta:
32
32
  author = "enzok"
@@ -45,8 +45,9 @@ yara_rules = yara.compile(
45
45
  condition:
46
46
  4 of them
47
47
  }
48
- """
49
- )
48
+ """)
49
+
50
+ MIN_CHARS = 6
50
51
 
51
52
 
52
53
  def transform(src, lookup_table):
@@ -63,37 +64,137 @@ def transform(src, lookup_table):
63
64
  result = lookup_table[n]
64
65
  src[pVal] = result
65
66
  pVal -= 1
67
+
66
68
  return src
67
69
 
68
70
 
69
71
  def yara_scan(raw_data):
70
72
  try:
71
73
  return yara_rules.match(data=raw_data)
72
- except Exception as e:
73
- print(e)
74
+ except yara.Error as e:
75
+ log.error("Yara scan failed: %s", e)
76
+ return []
77
+
78
+
79
+ def extract_utf16le(data, min_chars=MIN_CHARS):
80
+ results = []
81
+ n = len(data)
82
+ i = 0
83
+ while i < n - 1:
84
+ if 32 <= data[i] <= 126 and data[i + 1] == 0x00:
85
+ start = i
86
+ chars = []
87
+ while i < n - 1 and 32 <= data[i] <= 126 and data[i + 1] == 0x00:
88
+ chars.append(chr(data[i]))
89
+ i += 2
90
+
91
+ if len(chars) >= min_chars:
92
+ results.append((start, ''.join(chars)))
93
+ else:
94
+ i += 1
95
+
96
+ return results
97
+
98
+
99
+ def is_uri(s: str) -> bool:
100
+ return s.lower().lstrip().startswith(("api/", "/api"))
101
+
102
+
103
+ def is_c2(s: str) -> bool:
104
+ DOMAIN_RE = re.compile(r'^[a-zA-Z0-9.-]+\.(?:com|net)$', re.IGNORECASE)
105
+ IP_RE = re.compile(r'^(?:\d{1,3}\.){3}\d{1,3}$')
106
+
107
+ s = s.strip()
108
+ if DOMAIN_RE.match(s):
109
+ return True
110
+
111
+ if IP_RE.match(s):
112
+ try:
113
+ ip = ipaddress.IPv4Address(s)
114
+ if ip.is_private or ip.is_loopback or ip.is_link_local or ip.is_multicast or ip.is_reserved or ip.is_unspecified:
115
+ return False
116
+
117
+ return True
118
+ except ipaddress.AddressValueError:
119
+ return False
120
+
121
+ return False
122
+
123
+
124
+ def is_useragent(s: str) -> bool:
125
+ return "mozilla" in s.lower()
126
+
127
+
128
+ def is_mutex(s: str) -> bool:
129
+ if s.count('-') < 2:
130
+ return False
131
+
132
+ parts = [p.strip() for p in s.split('-') if p.strip()]
133
+ if len(parts) < 3:
134
+ return False
135
+
136
+ has_digit = any(any(ch.isdigit() for ch in part) for part in parts)
137
+ long_enough = all(len(part) > 1 for part in parts)
138
+ return has_digit and long_enough
139
+
140
+
141
+ def extract_types(data: bytes):
142
+ uris = []
143
+ c2s = []
144
+ user_agent = None
145
+ mutexes = []
146
+
147
+ for off, s in extract_utf16le(data):
148
+ s_str = s.strip()
149
+ if is_uri(s_str) and s_str not in uris:
150
+ uris.append(s_str)
151
+
152
+ if is_c2(s_str) and s_str not in c2s:
153
+ c2s.append(s_str)
154
+
155
+ if user_agent is None and is_useragent(s_str):
156
+ user_agent = s_str
157
+
158
+ if is_mutex(s_str) and s_str not in mutexes:
159
+ mutexes.append(s_str)
160
+
161
+ return uris, c2s, user_agent, mutexes
162
+
163
+
164
+ def make_endpoints(c2s: List[str], uris: List[str]) -> List[str]:
165
+ endpoints = []
166
+ for c2 in c2s:
167
+ for uri in uris:
168
+ endpoints.append(f"https://{c2}/{uri.lstrip('/')}")
169
+
170
+ return endpoints
74
171
 
75
172
 
76
173
  def extract_config(filebuf):
77
- yara_hit = yara_scan(filebuf)
174
+ yara_hits = yara_scan(filebuf)
78
175
  config = {}
79
176
 
80
- for hit in yara_hit:
177
+ for hit in yara_hits:
81
178
  if hit.rule == "Oyster":
82
179
  start_offset = ""
83
180
  lookup_va = ""
181
+
84
182
  for item in hit.strings:
85
183
  if "$start_exit" == item.identifier:
86
184
  start_offset = item.instances[0].offset
185
+
87
186
  if "$decode" == item.identifier:
88
187
  decode_offset = item.instances[0].offset
89
- lookup_va = filebuf[decode_offset + 12 : decode_offset + 16]
188
+ lookup_va = filebuf[decode_offset + 12: decode_offset + 16]
189
+
90
190
  if not (start_offset and lookup_va):
91
- return
191
+ continue
192
+
92
193
  try:
93
194
  pe = pefile.PE(data=filebuf, fast_load=True)
94
195
  lookup_offset = pe.get_offset_from_rva(struct.unpack("I", lookup_va)[0] - pe.OPTIONAL_HEADER.ImageBase)
95
- lookup_table = filebuf[lookup_offset : lookup_offset + 256]
96
- data = filebuf[start_offset + 4 : start_offset + 8092]
196
+ lookup_table = filebuf[lookup_offset: lookup_offset + 256]
197
+ data = filebuf[start_offset + 4: start_offset + 8092]
97
198
  hex_strings = re.split(rb"\x00+", data)
98
199
  hex_strings = [s for s in hex_strings if s]
99
200
  str_vals = []
@@ -105,8 +206,10 @@ def extract_config(filebuf):
105
206
  for item in hex_strings:
106
207
  with suppress(Exception):
107
208
  decoded = transform(bytearray(item), bytearray(lookup_table)).decode("utf-8")
209
+
108
210
  if not decoded:
109
211
  continue
212
+
110
213
  if "http" in decoded:
111
214
  if "\r\n" in decoded:
112
215
  c2.extend(list(filter(None, decoded.split("\r\n"))))
@@ -121,15 +224,19 @@ def extract_config(filebuf):
121
224
  if c2_matches:
122
225
  c2.extend(c2_matches)
123
226
 
124
- config = {
125
- "CNCs": c2,
126
- 'version': dll_version,
127
- "raw": {
128
- "Strings": str_vals,
129
- },
130
- }
227
+ config = {"CNCs": c2, 'version': dll_version, "raw": {"Strings": str_vals, }, }
228
+ return config
131
229
  except Exception as e:
132
230
  log.error("Error: %s", e)
231
+
232
+ if not config:
233
+ urls = []
234
+ uris, c2s, useragent, mutexes = extract_types(filebuf)
235
+ if uris and c2s:
236
+ urls = make_endpoints(c2s, uris)
237
+
238
+ config = {"CNCs": urls, "user_agent": useragent, "mutex": mutexes, }
239
+
133
240
  return config
134
241
 
135
242
 
@@ -6,7 +6,6 @@ from contextlib import suppress
6
6
  from io import BytesIO
7
7
 
8
8
  import pefile
9
-
10
9
  import yara
11
10
 
12
11
  rule_source = """
@@ -83,7 +82,7 @@ def get_c2s(data, count):
83
82
  c2_size = struct.unpack("I", data.read(4))[0]
84
83
  c2 = get_wchar_string(data, c2_size)
85
84
  port, val1, val2 = struct.unpack("III", data.read(12))
86
- c2_list.append(f"{c2}:{port}")
85
+ c2_list.append(f"http://{c2}:{port}")
87
86
  return c2_list
88
87
 
89
88
 
@@ -5,22 +5,21 @@
5
5
  DESCRIPTION = "Qakbot configuration parser."
6
6
  AUTHOR = "threathive, r1n9w0rm"
7
7
 
8
- import sys
9
8
  import datetime
10
9
  import hashlib
11
10
  import ipaddress
12
11
  import logging
13
12
  import socket
14
13
  import struct
14
+ import sys
15
15
  from contextlib import suppress
16
16
 
17
17
  import pefile
18
+ import yara
18
19
  from Cryptodome.Cipher import AES, ARC4
19
20
  from Cryptodome.Hash import SHA256
20
21
  from Cryptodome.Util.Padding import unpad
21
22
 
22
- import yara
23
-
24
23
  try:
25
24
  HAVE_BLZPACK = True
26
25
  from cape_parsers.utils import blzpack
@@ -494,7 +493,11 @@ def extract_config(filebuf):
494
493
  for k, v in config.items():
495
494
  end_config.setdefault(k, v)
496
495
  if end_config:
497
- return {"raw": end_config}
496
+ output = {"raw": end_config}
497
+ if "C2s" in end_config:
498
+ # Prefix with tcp://
499
+ output["CNCs"] = [f"http://{c2}" for c2 in end_config["C2s"]]
500
+ return output
498
501
 
499
502
 
500
503
  if __name__ == "__main__":
@@ -118,10 +118,11 @@ def extract_config(filebuf):
118
118
  cfg["campaign"] = campaign
119
119
 
120
120
  if c2s:
121
- cfg["CNCs"] = c2s
121
+ cfg["CNCs"] = [f"http://{c2}" for c2 in c2s]
122
122
 
123
123
  if mutexes:
124
- cfg["mutex"] = list(set(mutexes))
124
+ mutexes = list(set(mutexes))
125
+ cfg["mutex"] = mutexes[0] if len(mutexes) == 1 else mutexes
125
126
 
126
127
  return cfg
127
128
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: CAPE-parsers
3
- Version: 0.1.55
3
+ Version: 0.1.57
4
4
  Summary: CAPE: Malware Configuration Extraction
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -1,55 +1,56 @@
1
1
  cape_parsers/CAPE/__init__.py,sha256=JcY8WPKzUFYgexwV1eyKIuT1JyNZzMJjBynlPSzxY_I,7
2
- cape_parsers/CAPE/community/AgentTesla.py,sha256=ln5MqFXkTb7WrlDrUHNTnMWBYRHDSqyK4VHeq0ZldtA,4047
2
+ cape_parsers/CAPE/community/AgentTesla.py,sha256=NmKIZRAea2aEJOszKyHDyiwljFAGIe4oe6aOXiyIb6Q,4413
3
3
  cape_parsers/CAPE/community/Amadey.py,sha256=IUyt909q9IDQPPip6UW9uD16rJMD_gvkwvNZ8NHTW-k,5577
4
+ cape_parsers/CAPE/community/Amatera.py,sha256=8s94SW58r04bs2KuIdJJ5-dm4WfoOrwINEqdSuFJKHk,7096
4
5
  cape_parsers/CAPE/community/Arkei.py,sha256=k36qHxdo5yPa9V1cg7EImSWP06kMog0rBda4KXqLKCY,3783
5
6
  cape_parsers/CAPE/community/AsyncRAT.py,sha256=0-FRT3d2x63KQ_cs1xmKFj7x0JRf7ID6QDc_DvBa0PM,1003
6
7
  cape_parsers/CAPE/community/AuroraStealer.py,sha256=LRu2QFBYkGhRGDJBw3GlcKub4E0_TBWmjdR2PnobDZM,2643
7
- cape_parsers/CAPE/community/Carbanak.py,sha256=Smi_vTWDfWxYBQa661ZIy0624IYJA22LMHAJEQbstpk,5607
8
+ cape_parsers/CAPE/community/Carbanak.py,sha256=K8wPh-geoeNOT2NPQJb9fJhsuo2dCgC-2A9-fV_3Dkc,5837
8
9
  cape_parsers/CAPE/community/CobaltStrikeBeacon.py,sha256=U4Q0ObCrPRpiO5B5fBmkgr63jXdizujNth8v6kUPnEQ,19466
9
10
  cape_parsers/CAPE/community/CobaltStrikeStager.py,sha256=HLxROBjz453uHNq1bPz0VSAhtyWDfz79ZacTPdjuWmY,7535
10
11
  cape_parsers/CAPE/community/DCRat.py,sha256=0-FRT3d2x63KQ_cs1xmKFj7x0JRf7ID6QDc_DvBa0PM,1003
11
- cape_parsers/CAPE/community/Fareit.py,sha256=OyKeZdcvyAhjxZgJqkDPJHP4Npv1ArvTHJZ5F0C1Iac,1875
12
- cape_parsers/CAPE/community/KoiLoader.py,sha256=cWV3TcVz5Qg7kkmMYYtuh5cXhAKohGAk6d6aXuId0lc,4077
12
+ cape_parsers/CAPE/community/Fareit.py,sha256=8LFQSgC12onQ8Fp0VNTWPjhMD07BnGQ6zySrbDeMHQM,1877
13
+ cape_parsers/CAPE/community/KoiLoader.py,sha256=tCl22pzNnuzC9dvlh13oa_KEBlMwchrEhy5KjipfyiM,4048
13
14
  cape_parsers/CAPE/community/LokiBot.py,sha256=355kqLx0LNMr8XcGfPL7cxG8QZalcmE7ttVBqoWtTWE,5754
14
15
  cape_parsers/CAPE/community/Lumma.py,sha256=Iqd9yvt3g0FeV_bYRmL1RKp4C1H92qeGg4fXivVDSxw,12206
15
16
  cape_parsers/CAPE/community/MonsterV2.py,sha256=cFxhYxo7FruTMmFY3OtBO-E0hDyxfsC3zWX3BlcB-qI,2915
16
17
  cape_parsers/CAPE/community/MyKings.py,sha256=bcypBMJf6Jeg0yrHZ-J-XjnhHvFbb-lpDF_zwW63gOk,1397
17
- cape_parsers/CAPE/community/NanoCore.py,sha256=8QZnf1AcY9481kSfsf3SHQShwPLn97peGAf8_xEasQc,6230
18
+ cape_parsers/CAPE/community/NanoCore.py,sha256=bfrzyKG6nvK9psuax2vxHDZvAnGfHKoC0sXe_G7TnYU,6588
18
19
  cape_parsers/CAPE/community/Nighthawk.py,sha256=8ss8yvslrwUt53zV6U0xuwGKU3hgYfOt13S5lkOVpNo,12105
19
- cape_parsers/CAPE/community/Njrat.py,sha256=GiwSENBB43RUqyJ7zT7ZPkPUYqo8Ew4kd5MJUj0jzdc,4702
20
+ cape_parsers/CAPE/community/Njrat.py,sha256=nA9nsbHTg8LlwIHprTjvySKIkKyPgckwYG3y8ymx6IQ,4708
20
21
  cape_parsers/CAPE/community/PhemedroneStealer.py,sha256=Z7_PdxC8bmd6P3AqOm7AHVRrbEVuREwMWbyLVHaAhK0,7095
21
22
  cape_parsers/CAPE/community/QuasarRAT.py,sha256=dzVInOc-BPVRdArk92oEY4PKq1AEW04NUToL8UV-UGk,146
22
23
  cape_parsers/CAPE/community/README.md,sha256=SHgVQraCdp033IQjM4Cm6t70U4kULn1MfSwTq3rsZv8,22
23
- cape_parsers/CAPE/community/Snake.py,sha256=v_MAPmg86ZdgGOkzc9GVHbi-lu4nLa1_0Lp90qiCg8s,6650
24
- cape_parsers/CAPE/community/SparkRAT.py,sha256=OVDty_1i9PTGuEumT0BHoDn0bD2UtdhHVNjThah80pg,2140
24
+ cape_parsers/CAPE/community/Snake.py,sha256=_ft5VJup4x7LuNGPCo95M-cNwoyH4t0yffqHLVEmy84,6654
25
+ cape_parsers/CAPE/community/SparkRAT.py,sha256=Ef37A3ZXxBkBYcARQ8h0BxnrEQL7EOP9cneP-u2rV5Y,2350
25
26
  cape_parsers/CAPE/community/Stealc.py,sha256=18EkQ-lMMAreKV5vA9xLBmOK5B4JtYcBwVqNfof4K2A,5321
26
27
  cape_parsers/CAPE/community/VenomRAT.py,sha256=0-FRT3d2x63KQ_cs1xmKFj7x0JRf7ID6QDc_DvBa0PM,1003
27
- cape_parsers/CAPE/community/WinosStager.py,sha256=6jkfTJiVdz6oyyqTjh3I4qunYgXbD4qXS_bd-uogwGA,2407
28
+ cape_parsers/CAPE/community/WinosStager.py,sha256=PVOGM1Cj-WSO3Vq-Yti9TS9XlNbrHG6ZhaE4Tm2VuOM,2554
28
29
  cape_parsers/CAPE/community/XWorm.py,sha256=0-FRT3d2x63KQ_cs1xmKFj7x0JRf7ID6QDc_DvBa0PM,1003
29
30
  cape_parsers/CAPE/community/XenoRAT.py,sha256=0-FRT3d2x63KQ_cs1xmKFj7x0JRf7ID6QDc_DvBa0PM,1003
30
31
  cape_parsers/CAPE/community/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
- cape_parsers/CAPE/core/AdaptixBeacon.py,sha256=40wMfrXt-7UG30WsLC5GxUtG6tSUaaP1OT-ntWzPZn0,2956
32
+ cape_parsers/CAPE/core/AdaptixBeacon.py,sha256=j1PN5yYQG9smdzU8lHtIy7wXYZRYPC2doegwNuTb7E4,3462
32
33
  cape_parsers/CAPE/core/AuraStealer.py,sha256=RSiclflsvcrcNLHpRokc_qF2cdQKXGBKg8Ti-Q-XmaM,3021
33
34
  cape_parsers/CAPE/core/Azorult.py,sha256=YkMIhC6zRTxEkLVMUdr2MMsbV9iAnZ8hUS8be9GZ5N4,2150
34
35
  cape_parsers/CAPE/core/BitPaymer.py,sha256=HQwoE0o7HMiXItxE08vBenf2ZWMxZp84-Hf_1eZ8QdE,3050
35
36
  cape_parsers/CAPE/core/BlackDropper.py,sha256=sCSu2T5oPvcFHlSAzSsLj_gCv2Tldl0UPguwy0MVg6A,3282
36
- cape_parsers/CAPE/core/Blister.py,sha256=wprcJMHixv4JHGqBjQeu26BJ6HgXeBMobh10Y-H6-Xg,18173
37
+ cape_parsers/CAPE/core/Blister.py,sha256=3lZamTW4aWcpsv1AGykhdrrhiGoWOY_EhXUeB2QUbvs,18268
37
38
  cape_parsers/CAPE/core/BruteRatel.py,sha256=_hFAYLbOsHdekWPOMXRmIYNXTNeNQSs3LZqh7xAVI2U,1147
38
39
  cape_parsers/CAPE/core/BumbleBee.py,sha256=8a15OLAoNKs377ehdgx8R6kCAsJeG-_itt8xc_EMQes,10075
39
- cape_parsers/CAPE/core/DarkGate.py,sha256=ppSRDfw-u2NltzQlrVvRwqxGaprShuv5CrwbNbnSvaw,3477
40
+ cape_parsers/CAPE/core/DarkGate.py,sha256=SH_lCQBCzayUw48uuX_Pn70unW8Dt2P-LqQYOdu1Ca4,4548
40
41
  cape_parsers/CAPE/core/DoppelPaymer.py,sha256=LPAQ-7imcAWFciAd7Qb_r6js2PdIsTt9fRdYKoEkFMg,2537
41
42
  cape_parsers/CAPE/core/DridexLoader.py,sha256=8NKppvGz7tVXnNTGEgS7R3LGn5vtW4xslQYbo38wQUg,7087
42
43
  cape_parsers/CAPE/core/Formbook.py,sha256=rvf0BRuRl_v8K9SJuSSfbVVMWLSTEemIgP3NtPp2vFM,550
43
44
  cape_parsers/CAPE/core/GuLoader.py,sha256=wH6t1e7rO60Bwe0ulqFdZq12-M087zT5WQtC_Wn2biU,354
44
45
  cape_parsers/CAPE/core/IcedID.py,sha256=TEsvFq8qHz_D5kIURKWSC4lbvWaQbMriDZ3jQsVu2VA,4029
45
- cape_parsers/CAPE/core/IcedIDLoader.py,sha256=YUOEILpTycO01KK4qqAxGSplsRVs2EzjscUw4T-DGWs,1602
46
- cape_parsers/CAPE/core/Latrodectus.py,sha256=1K9yUUYtzRJ2c3unrYIUaA8nE--Zoqi5pjXY7t7t1qg,7751
47
- cape_parsers/CAPE/core/NitroBunnyDownloader.py,sha256=nxySGP7t5yv7-YL31-IiMxzkKCYKrDQgzWfMXadha2g,5265
48
- cape_parsers/CAPE/core/Oyster.py,sha256=QStBScevJuLyd5d4Rw093SxTlbRG1LFkDwYgmjZx-EQ,4881
49
- cape_parsers/CAPE/core/PikaBot.py,sha256=6Q8goXfMsSoU8UkdE9iuZY2KTxX_AmWhH1szke_HfWA,5280
46
+ cape_parsers/CAPE/core/IcedIDLoader.py,sha256=RM7DgpAD_EzQZKn9LdmdophJpkjJHassgd9bT7NSClA,1634
47
+ cape_parsers/CAPE/core/Latrodectus.py,sha256=5QQKB93IldxnGv78mlozBgsu9NSHypA-qmJrJ1ufD0c,7753
48
+ cape_parsers/CAPE/core/NitroBunnyDownloader.py,sha256=A1mFjRP_yLPEKfsAttiOOmSiEBHTiSSzcmUZ6duDE3A,5285
49
+ cape_parsers/CAPE/core/Oyster.py,sha256=7h7panvLV1Tt1pAynk32-m-AMeGiPAKqSD4FGTHOY7I,7490
50
+ cape_parsers/CAPE/core/PikaBot.py,sha256=t2_TXC8fK7kVFJP6kmNFw0agRepO3R92T0e4eSxG2AU,5286
50
51
  cape_parsers/CAPE/core/PlugX.py,sha256=lGwr1T3mttG6CTbZCj_Cf5HnOad60A3LP264jlCsGsc,13192
51
- cape_parsers/CAPE/core/QakBot.py,sha256=SmXRuwOiaDLL7uN9RwCiQP62P3ctxGJ6y54zJG9yuyM,18230
52
- cape_parsers/CAPE/core/Quickbind.py,sha256=5A077RFQQOL8dtr2Q9vmlTKsWk96JkRWuHGseApyTmU,3675
52
+ cape_parsers/CAPE/core/QakBot.py,sha256=iIbe_aE9mlip8esp10FAEjNsiaOfTtXnygu8E3JJQKI,18392
53
+ cape_parsers/CAPE/core/Quickbind.py,sha256=0nyUatmDf79tWhjR3IM2pDFV98cmly9SfpULHdJL6JQ,3769
53
54
  cape_parsers/CAPE/core/README.md,sha256=Zd84WEUj9NzKzGnVZV1jx6gMiEOtz01m32B7xEuS91k,17
54
55
  cape_parsers/CAPE/core/RedLine.py,sha256=bZeKLvxaS6HDpWY4RDXtSEBt93qTNzZG5iE6FNS0dOY,5734
55
56
  cape_parsers/CAPE/core/Remcos.py,sha256=MIpO2FwehBGIhO7hS0TT2hdDsgvxlI5ps4rAwyFwdTY,9483
@@ -111,7 +112,7 @@ cape_parsers/utils/blzpack_lib.so,sha256=5PJtnggw8fV5q4DlhwMJk4ZadvC3fFTsVTNZKvE
111
112
  cape_parsers/utils/dotnet_utils.py,sha256=pzQGbCqccz7DRv8T_i1JURlrKDIlDT2axxViiFF9hsU,1672
112
113
  cape_parsers/utils/lznt1.py,sha256=X-BmJtP6AwYSl0ORg5dfSt-NIuXbHrtCO5kUaaJI2C8,4066
113
114
  cape_parsers/utils/strings.py,sha256=a-nbvP9jYST7b6t_H37Ype-fK2jEmQr-wMF5a4i04e4,3062
114
- cape_parsers-0.1.55.dist-info/METADATA,sha256=eQvSMB-kC-_Hrdc78Sv-uxnNrVywaTqlLf_cjIG-gy8,1826
115
- cape_parsers-0.1.55.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
116
- cape_parsers-0.1.55.dist-info/licenses/LICENSE,sha256=88c01_HLG8WPj7R7aU_b-O-UoF38vrrifvcko4KDxcE,1069
117
- cape_parsers-0.1.55.dist-info/RECORD,,
115
+ cape_parsers-0.1.57.dist-info/METADATA,sha256=tKJbd4dpuBIznH1pffa7cSURQUmWCrhaJRHOa-YFAxs,1826
116
+ cape_parsers-0.1.57.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
117
+ cape_parsers-0.1.57.dist-info/licenses/LICENSE,sha256=88c01_HLG8WPj7R7aU_b-O-UoF38vrrifvcko4KDxcE,1069
118
+ cape_parsers-0.1.57.dist-info/RECORD,,