CAPE-parsers 0.1.58__py3-none-any.whl → 0.1.60__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.
@@ -28,6 +28,34 @@ rule Amadey_Key_String
28
28
  }
29
29
  """
30
30
 
31
+ RULE_SOURCE_KEY_X64 = """
32
+ rule Amadey_Key_String_X64
33
+ {
34
+ meta:
35
+ author = "Matthieu Gras"
36
+ description = "Find decryption key in Amadey (64bit)."
37
+ strings:
38
+ $opcodes = {
39
+ /* sub rsp, imm8 */
40
+ 48 83 EC ??
41
+
42
+ /* mov r8d, imm32 */
43
+ 41 B8 ?? ?? ?? ??
44
+
45
+ /* lea rdx, [rip+disp32] */
46
+ 48 8D 15 ?? ?? ?? ??
47
+
48
+ /* lea rcx, [rip+disp32] */
49
+ 48 8D 0D ?? ?? ?? ??
50
+
51
+ /* call rel32 */
52
+ E8 ?? ?? ?? ??
53
+ }
54
+ condition:
55
+ $opcodes
56
+ }
57
+ """
58
+
31
59
  RULE_SOURCE_ENCODED_STRINGS = """
32
60
  rule Amadey_Encoded_Strings
33
61
  {
@@ -50,6 +78,43 @@ rule Amadey_Encoded_Strings
50
78
  }
51
79
  """
52
80
 
81
+ RULE_SOURCE_ENCODED_STRINGS_X64 = """
82
+ rule Amadey_Encoded_Strings_X64
83
+ {
84
+ meta:
85
+ author = "Matthieu Gras"
86
+ description = "Find encoded strings in Amadey (64bit)."
87
+ strings:
88
+ $opcodes = {
89
+ /* sub rsp, imm8 */
90
+ 48 83 EC ??
91
+
92
+ /* mov r8d, imm32 */
93
+ 41 B8 ?? ?? ?? ??
94
+
95
+ /* lea rdx, [rip+disp32] */
96
+ 48 8D 15 ?? ?? ?? ??
97
+
98
+ /* lea rcx, [rip+disp32] */
99
+ 48 8D 0D ?? ?? ?? ??
100
+
101
+ /* call rel32 */
102
+ E8 ?? ?? ?? ??
103
+
104
+ /* lea rcx, [rip+disp32] */
105
+ 48 8D 0D ?? ?? ?? ??
106
+
107
+ /* add rsp, imm8 */
108
+ 48 83 C4 ??
109
+
110
+ /* jmp rel32 */
111
+ E9 ?? ?? ?? ??
112
+ }
113
+ condition:
114
+ $opcodes
115
+ }
116
+ """
117
+
53
118
 
54
119
  def contains_non_printable(byte_array):
55
120
  for byte in byte_array:
@@ -68,14 +133,14 @@ def yara_scan_generator(raw_data, rule_source):
68
133
  yield instance.offset, block.identifier
69
134
 
70
135
 
71
- def get_keys(pe, data):
72
- image_base = pe.OPTIONAL_HEADER.ImageBase
136
+ def get_keys(pe, data, is_64bit=False):
73
137
  keys = []
74
- for offset, _ in yara_scan_generator(data, RULE_SOURCE_KEY):
138
+ rule_source = RULE_SOURCE_KEY_X64 if is_64bit else RULE_SOURCE_KEY
139
+
140
+ for offset, _ in yara_scan_generator(data, rule_source):
75
141
  try:
76
- key_string_rva = struct.unpack('i', data[offset + 3 : offset + 7])[0]
77
- key_string_dword_offset = pe.get_offset_from_rva(key_string_rva - image_base)
78
- key_string = pe.get_string_from_data(key_string_dword_offset, data)
142
+ key_string_offset = get_rip_relative_address(pe, data, offset, is_64bit)
143
+ key_string = pe.get_string_from_data(key_string_offset, data)
79
144
 
80
145
  if b"=" not in key_string:
81
146
  keys.append(key_string.decode())
@@ -88,19 +153,34 @@ def get_keys(pe, data):
88
153
 
89
154
  return []
90
155
 
156
+ def get_rip_relative_address(pe, data, offset, is_64bit):
157
+ if is_64bit:
158
+ offset += 10
159
+ disp = struct.unpack('<i', data[offset + 3 : offset + 7])[0]
160
+ rip_rva = pe.get_rva_from_offset(offset + 7)
161
+ target_rva = rip_rva + disp
162
+ target_offset = pe.get_offset_from_rva(target_rva)
163
+ else:
164
+ rva = struct.unpack('i', data[offset + 3 : offset + 7])[0]
165
+ target_offset = pe.get_offset_from_rva(rva - pe.OPTIONAL_HEADER.ImageBase)
91
166
 
92
- def get_encoded_strings(pe, data):
167
+ return target_offset
168
+
169
+ def get_encoded_strings(pe, data, is_64bit=False):
93
170
  encoded_strings = []
94
- image_base = pe.OPTIONAL_HEADER.ImageBase
95
- for offset, _ in yara_scan_generator(data, RULE_SOURCE_ENCODED_STRINGS):
171
+ rule_source = RULE_SOURCE_ENCODED_STRINGS_X64 if is_64bit else RULE_SOURCE_ENCODED_STRINGS
172
+
173
+ for offset, _ in yara_scan_generator(data, rule_source):
96
174
 
97
175
  try:
98
- encoded_string_size = data[offset + 1]
99
- encoded_string_rva = struct.unpack('i', data[offset + 3 : offset + 7])[0]
100
- encoded_string_dword_offset = pe.get_offset_from_rva(encoded_string_rva - image_base)
101
- encoded_string = pe.get_string_from_data(encoded_string_dword_offset, data)
176
+ if is_64bit:
177
+ encoded_string_size = struct.unpack('<I', data[offset + 6 : offset + 10])[0]
178
+ else:
179
+ encoded_string_size = data[offset + 1]
180
+
181
+ encoded_string_offset = get_rip_relative_address(pe, data, offset, is_64bit)
182
+ encoded_string = pe.get_string_from_data(encoded_string_offset, data)
102
183
 
103
- # Make sure the string matches length from operand
104
184
  if encoded_string_size != len(encoded_string):
105
185
  continue
106
186
 
@@ -147,13 +227,15 @@ def extract_config(data):
147
227
  pe = pefile.PE(data=data, fast_load=True)
148
228
  # image_base = pe.OPTIONAL_HEADER.ImageBase
149
229
 
150
- keys = get_keys(pe, data)
230
+ is_64bit = pe.OPTIONAL_HEADER.Magic == pefile.OPTIONAL_HEADER_MAGIC_PE_PLUS
231
+
232
+ keys = get_keys(pe, data, is_64bit)
151
233
  if not keys:
152
234
  return {}
153
235
 
154
236
  decode_key = keys[0]
155
237
  rc4_key = keys[1]
156
- encoded_strings = get_encoded_strings(pe, data)
238
+ encoded_strings = get_encoded_strings(pe, data, is_64bit)
157
239
 
158
240
  decoded_strings = []
159
241
  for encoded_string in encoded_strings:
@@ -3,11 +3,8 @@ import json
3
3
  import pefile
4
4
  import yara
5
5
  import struct
6
- import re
7
- import ipaddress
8
6
  from contextlib import suppress
9
7
 
10
-
11
8
  DESCRIPTION = "Amatera Stealer parser"
12
9
  AUTHOR = "YungBinary"
13
10
 
@@ -18,7 +15,7 @@ rule AmateraDecrypt
18
15
  author = "YungBinary"
19
16
  description = "Find Amatera XOR key"
20
17
  strings:
21
- $decrypt = {
18
+ $decrypt_global = {
22
19
  A1 ?? ?? ?? ?? // mov eax, dword ptr ds:szXorKey ; "852149723"
23
20
  89 45 ?? // mov dword ptr [ebp+xor_key], eax
24
21
  8B 0D ?? ?? ?? ?? // mov ecx, dword ptr ds:szXorKey+4 ; "49723"
@@ -29,12 +26,18 @@ rule AmateraDecrypt
29
26
  50 // push eax
30
27
  E8 // call
31
28
  }
29
+ $decrypt_stack = {
30
+ 83 EC 1C // sub esp, 1Ch
31
+ 56 // push esi
32
+ 89 ?? ?? // mov [ebp+var], reg
33
+ C6 45 ?? ?? // mov [ebp+char_1], imm
34
+ C6 45 ?? ?? // mov [ebp+char_2], imm
35
+ }
32
36
  condition:
33
- uint16(0) == 0x5A4D and $decrypt
37
+ uint16(0) == 0x5A4D and ($decrypt_global or $decrypt_stack)
34
38
  }
35
39
  """
36
40
 
37
-
38
41
  RULE_SOURCE_AES_KEY = """
39
42
  rule AmateraAESKey
40
43
  {
@@ -83,28 +86,18 @@ rule AmateraAESKey
83
86
  }
84
87
  """
85
88
 
86
- DOMAIN_REGEX = r'^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$'
89
+ B64_CHARS = set(b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=")
87
90
 
88
91
 
89
92
  def yara_scan(raw_data: bytes, rule_source: str):
90
93
  yara_rules = yara.compile(source=rule_source)
91
94
  matches = yara_rules.match(data=raw_data)
92
-
93
95
  for match in matches:
94
96
  for block in match.strings:
95
97
  for instance in block.instances:
96
98
  return instance.offset
97
99
 
98
100
 
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
101
  def xor_data(data, key):
109
102
  decoded = bytearray()
110
103
  for i in range(len(data)):
@@ -112,68 +105,107 @@ def xor_data(data, key):
112
105
  return decoded
113
106
 
114
107
 
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
108
+ def decode_and_decrypt(data_bytes, xor_key):
109
+ if not data_bytes:
110
+ return ""
124
111
 
112
+ clean_bytes = data_bytes.rstrip(b"\x00")
113
+
114
+ # Heuristic: Check if valid Base64
115
+ if any(b not in B64_CHARS for b in clean_bytes):
116
+ return clean_bytes.decode("utf-8", errors="ignore")
125
117
 
126
- def is_valid_domain(data):
127
118
  try:
128
- if re.fullmatch(DOMAIN_REGEX, data.decode()):
129
- return True
130
- return False
119
+ decoded = base64.b64decode(clean_bytes, validate=True)
120
+ decrypted = xor_data(decoded, xor_key)
121
+ # Heuristic: Check if result is printable ASCII
122
+ if all(0x20 <= b <= 0x7E for b in decrypted if b != 0):
123
+ return decrypted.decode("utf-8", errors="ignore").rstrip("\x00")
131
124
  except Exception:
132
- return False
125
+ pass
126
+
127
+ return clean_bytes.decode("utf-8", errors="ignore")
133
128
 
134
129
 
135
130
  def extract_config(data):
136
- """
137
- Extract Amatera malware configuration.
138
- """
139
131
  config_dict = {}
140
132
 
141
133
  with suppress(Exception):
142
134
  pe = pefile.PE(data=data)
143
135
  image_base = pe.OPTIONAL_HEADER.ImageBase
144
136
 
145
- # Identify XOR key decryption routine and extract key
146
137
  offset = yara_scan(data, RULE_SOURCE)
147
- if not offset:
138
+ if offset is None:
148
139
  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
140
 
152
- # Extract AES 256 key
141
+ key_str = b""
142
+ if data[offset] == 0xA1: # $decrypt_global
143
+ key_str_va = struct.unpack("i", data[offset + 1 : offset + 5])[0]
144
+ key_str = (
145
+ pe.get_string_at_rva(key_str_va - image_base, max_length=20) + b"\x00"
146
+ )
147
+
148
+ elif data[offset] == 0x83: # $decrypt_stack
149
+ key_bytes = bytearray()
150
+ # Skip sub/push/mov reg (7 bytes)
151
+ current_idx = offset + 7
152
+ for _ in range(32):
153
+ if data[current_idx] != 0xC6 or data[current_idx + 1] != 0x45:
154
+ break
155
+ val = data[current_idx + 3]
156
+ if val == 0x00:
157
+ break
158
+ key_bytes.append(val)
159
+ current_idx += 4
160
+ key_str = key_bytes + b"\x00"
161
+
162
+ if key_str:
163
+ config_dict["xor_key"] = key_str.rstrip(b"\x00").decode(
164
+ "utf-8", errors="ignore"
165
+ )
166
+
153
167
  aes_key_offset = yara_scan(data, RULE_SOURCE_AES_KEY)
154
- aes_key = bytearray()
155
168
  if aes_key_offset:
169
+ aes_key = bytearray()
156
170
  aes_block = data[aes_key_offset : aes_key_offset + 131]
157
171
  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
172
+ aes_key.append(aes_block[i + 6])
173
+ config_dict["cryptokey"] = aes_key.hex()
174
+ config_dict["cryptokey_type"] = "AES"
175
+
176
+ data_section = next(
177
+ (
178
+ s
179
+ for s in pe.sections
180
+ if s.Name.decode().strip("\x00").lower() == ".data"
181
+ ),
182
+ None,
183
+ )
184
+ if data_section:
185
+ # First 16 bytes = 4 pointers
186
+ pointers_raw = pe.get_data(data_section.VirtualAddress, 16)
187
+ if len(pointers_raw) == 16:
188
+ ptrs = struct.unpack("4I", pointers_raw)
189
+ extracted = []
190
+
191
+ for ptr in ptrs:
192
+ rva = ptr - image_base
193
+ if 0 < rva < pe.OPTIONAL_HEADER.SizeOfImage:
194
+ extracted.append(pe.get_string_at_rva(rva))
195
+ else:
196
+ extracted.append(b"")
197
+
198
+ if len(extracted) == 4:
199
+ config_dict["payload_guid_1"] = decode_and_decrypt(
200
+ extracted[0], key_str
201
+ )
202
+ config_dict["payload_guid_2"] = decode_and_decrypt(
203
+ extracted[1], key_str
204
+ )
205
+ config_dict["fake_c2"] = decode_and_decrypt(extracted[2], key_str)
206
+
207
+ real_c2 = decode_and_decrypt(extracted[3], key_str)
208
+ config_dict["CNCs"] = [f"https://{real_c2}"]
177
209
 
178
210
  return config_dict
179
211
 
@@ -182,5 +214,4 @@ if __name__ == "__main__":
182
214
  import sys
183
215
 
184
216
  with open(sys.argv[1], "rb") as f:
185
- config_json = json.dumps(extract_config(f.read()), indent=4)
186
- print(config_json)
217
+ print(json.dumps(extract_config(f.read()), indent=4))
@@ -15,14 +15,15 @@
15
15
  DESCRIPTION = "Zloader configuration parser"
16
16
  AUTHOR = "kevoreilly"
17
17
 
18
+ import json
18
19
  import logging
20
+ import re
19
21
  import socket
20
22
  import struct
21
23
 
22
24
  import pefile
23
- from Cryptodome.Cipher import ARC4
24
-
25
25
  import yara
26
+ from Cryptodome.Cipher import ARC4
26
27
 
27
28
  log = logging.getLogger(__name__)
28
29
 
@@ -59,6 +60,20 @@ rule Zloader2024
59
60
  condition:
60
61
  uint16(0) == 0x5A4D and $conf_1 and 2 of ($confkey_*)
61
62
  }
63
+
64
+ rule Zloader2025
65
+ {
66
+ meta:
67
+ author = "enzok"
68
+ description = "Zloader Payload"
69
+ cape_type = "Zloader Payload"
70
+ strings:
71
+ $conf = {4? 01 ?? [4] E8 [4] 4? 8D 15 [4] 4? 89 ?? 4? 89 ?? E8 [4] C7 46 30 00 00 00 00 8B 7E 34}
72
+ $confkey_1 = {4? 01 ?? [2] E8 [4] 4? 8D 15 [4] 4? 89 ?? 4? 89 ?? E8 [4] C7 46 34 00 00 00 00 8B 46 38}
73
+ $confkey_2 = {4? 01 ?? [2] E8 [4] 4? 8D 15 [4] 4? 89 ?? 4? 89 ?? E8 [4] C7 46 38 00 00 00 00 48 83 C4 28}
74
+ condition:
75
+ uint16(0) == 0x5A4D and $conf and all of ($confkey_*)
76
+ }
62
77
  """
63
78
  MAX_STRING_SIZE = 32
64
79
 
@@ -71,41 +86,73 @@ def decrypt_rc4(key, data):
71
86
 
72
87
 
73
88
  def string_from_offset(data, offset):
74
- return data[offset : offset + MAX_STRING_SIZE].split(b"\0", 1)[0]
89
+ return data[offset: offset + MAX_STRING_SIZE].split(b"\0", 1)[0]
90
+
75
91
 
92
+ def parse_config(data, version=None):
93
+ for i in range(len(data) - 3, -1, -1):
94
+ if data[i : i + 3] == b"\x00\x00\x00":
95
+ data = data[:i]
96
+ break
76
97
 
77
- def parse_config(data):
78
98
  parsed = {}
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")
99
+ net_params = []
100
+ dns_ips = []
101
+ tls_sni = ""
102
+ cryptokey = ""
103
+ fields = [part.strip() for part in data.split(b'\x00') if part and part.strip()]
104
+ parsed["botnet"] = fields[0].decode("utf-8") if len(fields) > 0 else ""
105
+ parsed["campaign"] = fields[1].decode("utf-8") if len(fields) > 1 else ""
81
106
  c2s = []
82
- c2_data = data[46:686]
83
- for i in range(10):
84
- chunk = c2_data[i * 64 : (i + 1) * 64]
85
- chunk = chunk.rstrip(b"\x00")
86
- if chunk:
87
- c2s.append(chunk.decode("utf-8"))
107
+ for f in fields:
108
+ if f.startswith(b"http"):
109
+ f = f.decode("utf-8")
110
+ if "~" in f:
111
+ tls_sni, f = map(str.strip, f.split("~", 1))
112
+
113
+ if f:
114
+ c2s.append(f)
115
+
116
+ elif b"PUBLIC KEY" in f:
117
+ cryptokey = f.decode("utf-8").replace("\n", "")
118
+
119
+ elif version == 3 and b"\x08\x08" in f and len(f) % 4 == 0:
120
+ idx = 0
121
+ for i in range(len(f) // 4):
122
+ dns_ips.append(socket.inet_ntoa(f[idx: idx + 4]))
123
+ idx += 4
124
+
125
+ elif version == 4 and f.startswith(b"[") and f.endswith(b"]"):
126
+ try:
127
+ params = json.loads(f)
128
+ for param in params:
129
+ proto = param.get("proto", "unknown")
130
+ ip = param.get("ip", "")
131
+ port = param.get("port", 0)
132
+ qps = param.get("qps", "")
133
+ net_params.append(f"{proto}, {ip}:{port}, qps={qps}".strip())
134
+
135
+ except json.JSONDecodeError:
136
+ params = None
137
+
88
138
  parsed["CNCs"] = c2s
89
- parsed["cryptokey"] = data[704:].split(b"\x00", 1)[0]
139
+ parsed["cryptokey"] = cryptokey
90
140
  parsed["cryptokey_type"] = "RSA Public Key"
91
- dns_data = data[1004:].split(b"\x00", 1)[0]
92
- parsed["TLS SNI"] = dns_data.split(b"~")[0].decode("utf-8").rstrip()
93
- parsed["DNS C2"] = dns_data.split(b"~")[1].decode("utf-8").strip()
94
- dns_ips = []
95
- dns_ips.append(socket.inet_ntoa(data[1204:1208].split(b"\x00", 1)[0]))
96
- dns_ip_data = data[1208:1248]
97
- for i in range(10):
98
- chunk = dns_ip_data[i * 4 : (i + 1) * 4]
99
- chunk = chunk.rstrip(b"\x00")
100
- if chunk:
101
- dns_ips.append(socket.inet_ntoa(chunk))
102
- parsed["DNS Servers"] = dns_ips
141
+ raw = parsed["raw"] = {}
142
+ if tls_sni:
143
+ raw["tls sni"] = tls_sni
144
+
145
+ if net_params:
146
+ raw["dns config"] = net_params
147
+
148
+ if dns_ips:
149
+ raw["dns ips"] = dns_ips
150
+
103
151
  return parsed
104
152
 
105
153
 
106
154
  def extract_config(filebuf):
107
155
  config = {}
108
- end_config = {}
109
156
  pe = pefile.PE(data=filebuf, fast_load=False)
110
157
  image_base = pe.OPTIONAL_HEADER.ImageBase
111
158
  matches = yara_rules.match(data=filebuf)
@@ -113,7 +160,7 @@ def extract_config(filebuf):
113
160
  return
114
161
  conf_type = ""
115
162
  decrypt_key = ""
116
- conf_size = 1020
163
+ conf_size = None
117
164
  for match in matches:
118
165
  if match.rule == "Zloader":
119
166
  for item in match.strings:
@@ -137,17 +184,23 @@ def extract_config(filebuf):
137
184
  elif "$decrypt_key_3" == item.identifier:
138
185
  decrypt_key = item.instances[0].offset
139
186
  kva_s = 3
187
+ break
188
+
140
189
  elif match.rule == "Zloader2024":
141
- conf_size = 1264
190
+ conf_size = None
142
191
  rc4_chunk1 = None
143
192
  rc4_chunk2 = None
144
193
  numchunks = 0
145
194
  for item in match.strings:
146
- if "$conf_1" == item.identifier:
195
+ item_id = item.identifier
196
+ if item_id == "$conf_1":
147
197
  decrypt_conf = item.instances[0].offset + 6
148
198
  conf_size = item.instances[0].offset + 12
199
+ if conf_size > 2048:
200
+ conf_size = 2048
201
+
149
202
  conf_type = "3"
150
- elif item.identifier.startswith("$confkey_") and numchunks < 2:
203
+ elif item_id.startswith("$confkey_") and numchunks < 2:
151
204
  matched_data = item.instances[0].matched_data[:2]
152
205
  if matched_data == b"\x48\x8D":
153
206
  offset = 3
@@ -161,11 +214,32 @@ def extract_config(filebuf):
161
214
  rc4_chunk2 = chunk_offset
162
215
 
163
216
  numchunks += 1
217
+ break
218
+
219
+ elif match.rule == "Zloader2025":
220
+ conf_size = None
221
+ rc4_chunk1 = None
222
+ rc4_chunk2 = None
223
+ for item in match.strings:
224
+ item_id = item.identifier
225
+ if item_id == "$conf":
226
+ decrypt_conf = item.instances[0].offset + 15
227
+ size_base_offset = item.instances[0].offset + 5
228
+ call_func_offset = item.instances[0].offset + 7
229
+ call_func_size_offset = call_func_offset + 1
230
+ conf_type = "4"
231
+
232
+ elif item_id == "$confkey_1":
233
+ rc4_chunk1 = item.instances[0].offset + 13
234
+
235
+ elif item_id == "$confkey_2":
236
+ rc4_chunk2 = item.instances[0].offset + 13
237
+ break
164
238
 
165
239
  if conf_type == "1":
166
- va = struct.unpack("I", filebuf[decrypt_conf : decrypt_conf + 4])[0]
240
+ va = struct.unpack("I", filebuf[decrypt_conf: decrypt_conf + 4])[0]
167
241
  key = string_from_offset(filebuf, pe.get_offset_from_rva(va - image_base))
168
- data_offset = pe.get_offset_from_rva(struct.unpack("I", filebuf[decrypt_conf + 5 : decrypt_conf + 9])[0] - image_base)
242
+ data_offset = pe.get_offset_from_rva(struct.unpack("I", filebuf[decrypt_conf + 5: decrypt_conf + 9])[0] - image_base)
169
243
  enc_data = filebuf[data_offset:].split(b"\0\0", 1)[0]
170
244
  raw = decrypt_rc4(key, enc_data)
171
245
  items = list(filter(None, raw.split(b"\x00\x00")))
@@ -178,15 +252,17 @@ def extract_config(filebuf):
178
252
  elif len(item) == 16:
179
253
  config["cryptokey"] = item
180
254
  config["cryptokey_type"] = "RC4"
255
+
181
256
  elif conf_type == "2" and decrypt_key:
182
- conf_va = struct.unpack("I", filebuf[decrypt_conf + cva : decrypt_conf + cva + 4])[0]
257
+ conf_size = 1020
258
+ conf_va = struct.unpack("I", filebuf[decrypt_conf + cva: decrypt_conf + cva + 4])[0]
183
259
  conf_offset = pe.get_offset_from_rva(conf_va + pe.get_rva_from_offset(decrypt_conf) + cva + 4)
184
260
  # if not conf_size:
185
261
  # conf_size = struct.unpack("I", filebuf[decrypt_key + size_s : decrypt_key + size_s + 4])[0]
186
- key_va = struct.unpack("I", filebuf[decrypt_key + kva_s : decrypt_key + kva_s + 4])[0]
262
+ key_va = struct.unpack("I", filebuf[decrypt_key + kva_s: decrypt_key + kva_s + 4])[0]
187
263
  key_offset = pe.get_offset_from_rva(key_va + pe.get_rva_from_offset(decrypt_key) + kva_s + 4)
188
264
  key = string_from_offset(filebuf, key_offset)
189
- conf_data = filebuf[conf_offset : conf_offset + conf_size]
265
+ conf_data = filebuf[conf_offset: conf_offset + conf_size]
190
266
  raw = decrypt_rc4(key, conf_data)
191
267
  items = list(filter(None, raw.split(b"\x00\x00")))
192
268
  config["botnet"] = items[0].decode("utf-8")
@@ -198,25 +274,56 @@ def extract_config(filebuf):
198
274
  elif b"PUBLIC KEY" in item:
199
275
  config["cryptokey"] = item.decode("utf-8").replace("\n", "")
200
276
  config["cryptokey_type"] = "RSA Public key"
277
+
201
278
  elif conf_type == "3" and rc4_chunk1 and rc4_chunk2:
202
- conf_va = struct.unpack("I", filebuf[decrypt_conf : decrypt_conf + 4])[0]
279
+ conf_va = struct.unpack("I", filebuf[decrypt_conf: decrypt_conf + 4])[0]
203
280
  conf_offset = pe.get_offset_from_rva(conf_va + pe.get_rva_from_offset(decrypt_conf) + 4)
204
- conf_data = filebuf[conf_offset : conf_offset + conf_size]
205
- keychunk1_va = struct.unpack("I", filebuf[rc4_chunk1 : rc4_chunk1 + 4])[0]
281
+ conf_data = filebuf[conf_offset: conf_offset + conf_size]
282
+ keychunk1_va = struct.unpack("I", filebuf[rc4_chunk1: rc4_chunk1 + 4])[0]
206
283
  keychunk1_offset = pe.get_offset_from_rva(keychunk1_va + pe.get_rva_from_offset(rc4_chunk1) + 4)
207
- keychunk1 = filebuf[keychunk1_offset : keychunk1_offset + 16]
208
- keychunk2_va = struct.unpack("I", filebuf[rc4_chunk2 : rc4_chunk2 + 4])[0]
284
+ keychunk1 = filebuf[keychunk1_offset: keychunk1_offset + 16]
285
+ keychunk2_va = struct.unpack("I", filebuf[rc4_chunk2: rc4_chunk2 + 4])[0]
209
286
  keychunk2_offset = pe.get_offset_from_rva(keychunk2_va + pe.get_rva_from_offset(rc4_chunk2) + 4)
210
- keychunk2 = filebuf[keychunk2_offset : keychunk2_offset + 16]
287
+ keychunk2 = filebuf[keychunk2_offset: keychunk2_offset + 16]
211
288
  decrypt_key = bytes(a ^ b for a, b in zip(keychunk1, keychunk2))
212
289
  conf = decrypt_rc4(decrypt_key, conf_data)
213
- end_config = parse_config(conf)
290
+ config = parse_config(conf, 3)
291
+
292
+ elif conf_type == "4" and rc4_chunk1 and rc4_chunk2:
293
+ conf_va = struct.unpack("I", filebuf[decrypt_conf: decrypt_conf + 4])[0]
294
+ conf_offset = pe.get_offset_from_rva(conf_va + pe.get_rva_from_offset(decrypt_conf) + 4)
295
+ call_rva = pe.get_rva_from_offset(call_func_offset)
296
+ call_target_rva = call_rva + 5 + struct.unpack("i", filebuf[call_func_size_offset: call_func_size_offset + 4])[0]
297
+ call_target_offset = pe.get_offset_from_rva(call_target_rva)
298
+ function_tail = b"\x5F\x5E\xC3"
299
+ index = filebuf.find(function_tail, call_target_offset)
300
+ if index != -1:
301
+ index += 256
302
+
303
+ function_end_offset = index + len(function_tail)
304
+ function_data = filebuf[call_target_offset: function_end_offset]
305
+ pattern = re.compile(b"\x66\x81\xF1..\x66\x89\x4D.", re.DOTALL)
306
+ key = 0
307
+ for match in pattern.finditer(function_data):
308
+ off = match.start()
309
+ key = struct.unpack_from("<H", function_data, off + 3)[0]
214
310
 
215
- if config and end_config:
216
- config = config.update({"raw": end_config})
311
+ size_base = struct.unpack_from("<H", filebuf[size_base_offset: size_base_offset + 2])[0]
312
+ conf_size = size_base ^ key & 0xFFFF
313
+ conf_data = filebuf[conf_offset: conf_offset + conf_size]
314
+ keychunk1_va = struct.unpack("I", filebuf[rc4_chunk1: rc4_chunk1 + 4])[0]
315
+ keychunk1_offset = pe.get_offset_from_rva(keychunk1_va + pe.get_rva_from_offset(rc4_chunk1) + 4)
316
+ keychunk1 = filebuf[keychunk1_offset: keychunk1_offset + 16]
317
+ keychunk2_va = struct.unpack("I", filebuf[rc4_chunk2: rc4_chunk2 + 4])[0]
318
+ keychunk2_offset = pe.get_offset_from_rva(keychunk2_va + pe.get_rva_from_offset(rc4_chunk2) + 4)
319
+ keychunk2 = filebuf[keychunk2_offset: keychunk2_offset + 16]
320
+ decrypt_key = bytes(a ^ b for a, b in zip(keychunk1, keychunk2))
321
+ conf = decrypt_rc4(decrypt_key, conf_data)
322
+ config = parse_config(conf, 4)
217
323
 
218
324
  return config
219
325
 
326
+
220
327
  if __name__ == "__main__":
221
328
  import sys
222
329
  from pathlib import Path
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: CAPE-parsers
3
- Version: 0.1.58
3
+ Version: 0.1.60
4
4
  Summary: CAPE: Malware Configuration Extraction
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -1,7 +1,7 @@
1
1
  cape_parsers/CAPE/__init__.py,sha256=JcY8WPKzUFYgexwV1eyKIuT1JyNZzMJjBynlPSzxY_I,7
2
2
  cape_parsers/CAPE/community/AgentTesla.py,sha256=NmKIZRAea2aEJOszKyHDyiwljFAGIe4oe6aOXiyIb6Q,4413
3
- cape_parsers/CAPE/community/Amadey.py,sha256=IUyt909q9IDQPPip6UW9uD16rJMD_gvkwvNZ8NHTW-k,5577
4
- cape_parsers/CAPE/community/Amatera.py,sha256=8s94SW58r04bs2KuIdJJ5-dm4WfoOrwINEqdSuFJKHk,7096
3
+ cape_parsers/CAPE/community/Amadey.py,sha256=7a-meFa8-yKmz3SAhU7v-ZSl3rMwS7uODTorMn5NQHk,7494
4
+ cape_parsers/CAPE/community/Amatera.py,sha256=giu37eRWaHpVnKPyggp5S70NwT3UwYrsT9oEm6NmDpw,8738
5
5
  cape_parsers/CAPE/community/Arkei.py,sha256=k36qHxdo5yPa9V1cg7EImSWP06kMog0rBda4KXqLKCY,3783
6
6
  cape_parsers/CAPE/community/AsyncRAT.py,sha256=0-FRT3d2x63KQ_cs1xmKFj7x0JRf7ID6QDc_DvBa0PM,1003
7
7
  cape_parsers/CAPE/community/AuroraStealer.py,sha256=LRu2QFBYkGhRGDJBw3GlcKub4E0_TBWmjdR2PnobDZM,2643
@@ -60,7 +60,7 @@ cape_parsers/CAPE/core/Socks5Systemz.py,sha256=jSt6QejL5K99dIB3qdItvUHL28w6N60xu
60
60
  cape_parsers/CAPE/core/SquirrelWaffle.py,sha256=UMha7l60fL64VPHxueFUnCEGaO-CXau5ftEyK-Wv__o,3308
61
61
  cape_parsers/CAPE/core/Strrat.py,sha256=PAKTzGZCdblXr4pNKsOpNOPhvcaAfRCiE9BtKAeOp0M,2240
62
62
  cape_parsers/CAPE/core/WarzoneRAT.py,sha256=aHB6n-EX4uMZA93_R4yiFzRsvoqxfh7sdbtlAA-Ia2E,3780
63
- cape_parsers/CAPE/core/Zloader.py,sha256=Etjowu5fZOW7fFykPNOTDhLWjTcdvtPZUy3s6R8ln8M,9598
63
+ cape_parsers/CAPE/core/Zloader.py,sha256=h0G1FaU1z8iKy248yfYsrNxpRaDKde7DGn1k-DvxJKk,13734
64
64
  cape_parsers/CAPE/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
65
65
  cape_parsers/CAPE/core/test_cape.py,sha256=CrmghlO43hpnTLv0X8Dw4hTcrVHuJ0X20dPXcFpeWYo,31
66
66
  cape_parsers/RATDecoders/README.md,sha256=SHgVQraCdp033IQjM4Cm6t70U4kULn1MfSwTq3rsZv8,22
@@ -112,7 +112,7 @@ cape_parsers/utils/blzpack_lib.so,sha256=5PJtnggw8fV5q4DlhwMJk4ZadvC3fFTsVTNZKvE
112
112
  cape_parsers/utils/dotnet_utils.py,sha256=pzQGbCqccz7DRv8T_i1JURlrKDIlDT2axxViiFF9hsU,1672
113
113
  cape_parsers/utils/lznt1.py,sha256=X-BmJtP6AwYSl0ORg5dfSt-NIuXbHrtCO5kUaaJI2C8,4066
114
114
  cape_parsers/utils/strings.py,sha256=a-nbvP9jYST7b6t_H37Ype-fK2jEmQr-wMF5a4i04e4,3062
115
- cape_parsers-0.1.58.dist-info/METADATA,sha256=yK13F0B9baYnbtifb3kwzoFCaCcTHct7SbVFHBlixeU,1826
116
- cape_parsers-0.1.58.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
117
- cape_parsers-0.1.58.dist-info/licenses/LICENSE,sha256=88c01_HLG8WPj7R7aU_b-O-UoF38vrrifvcko4KDxcE,1069
118
- cape_parsers-0.1.58.dist-info/RECORD,,
115
+ cape_parsers-0.1.60.dist-info/METADATA,sha256=uC0gVg_hKpZuVrHSLzKie3I0QsGgFOahOkC-TOk8BMQ,1826
116
+ cape_parsers-0.1.60.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
117
+ cape_parsers-0.1.60.dist-info/licenses/LICENSE,sha256=88c01_HLG8WPj7R7aU_b-O-UoF38vrrifvcko4KDxcE,1069
118
+ cape_parsers-0.1.60.dist-info/RECORD,,