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