CAPE-parsers 0.1.56__tar.gz → 0.1.58__tar.gz
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-0.1.56 → cape_parsers-0.1.58}/PKG-INFO +1 -1
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/community/AgentTesla.py +7 -1
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/community/Carbanak.py +7 -2
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/community/Fareit.py +3 -3
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/community/NanoCore.py +11 -1
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/community/Njrat.py +1 -1
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/community/Snake.py +2 -2
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/community/SparkRAT.py +5 -1
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/community/WinosStager.py +6 -3
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/core/AdaptixBeacon.py +13 -1
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/core/Blister.py +4 -1
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/core/DarkGate.py +30 -1
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/core/IcedIDLoader.py +2 -1
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/core/Latrodectus.py +2 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/core/NitroBunnyDownloader.py +33 -29
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/core/Oyster.py +128 -21
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/core/PikaBot.py +1 -2
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/core/QakBot.py +7 -4
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/core/Quickbind.py +3 -2
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/core/SmokeLoader.py +16 -8
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/pyproject.toml +1 -1
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/LICENSE +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/README.md +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/__init__.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/community/Amadey.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/community/Amatera.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/community/Arkei.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/community/AsyncRAT.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/community/AuroraStealer.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/community/CobaltStrikeBeacon.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/community/CobaltStrikeStager.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/community/DCRat.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/community/KoiLoader.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/community/LokiBot.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/community/Lumma.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/community/MonsterV2.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/community/MyKings.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/community/Nighthawk.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/community/PhemedroneStealer.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/community/QuasarRAT.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/community/README.md +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/community/Stealc.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/community/VenomRAT.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/community/XWorm.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/community/XenoRAT.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/community/__init__.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/core/AuraStealer.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/core/Azorult.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/core/BitPaymer.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/core/BlackDropper.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/core/BruteRatel.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/core/BumbleBee.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/core/DoppelPaymer.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/core/DridexLoader.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/core/Formbook.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/core/GuLoader.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/core/IcedID.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/core/PlugX.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/core/README.md +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/core/RedLine.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/core/Remcos.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/core/Rhadamanthys.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/core/Socks5Systemz.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/core/SquirrelWaffle.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/core/Strrat.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/core/WarzoneRAT.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/core/Zloader.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/core/__init__.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/core/test_cape.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/RATDecoders/README.md +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/RATDecoders/__init__.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/RATDecoders/test_rats.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/__init__.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/deprecated/BackOffLoader.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/deprecated/BackOffPOS.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/deprecated/BlackNix.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/deprecated/BuerLoader.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/deprecated/ChChes.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/deprecated/Emotet.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/deprecated/Enfal.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/deprecated/EvilGrab.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/deprecated/Greame.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/deprecated/Hancitor.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/deprecated/HttpBrowser.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/deprecated/JavaDropper.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/deprecated/Nymaim.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/deprecated/Pandora.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/deprecated/PoisonIvy.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/deprecated/PredatorPain.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/deprecated/Punisher.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/deprecated/RCSession.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/deprecated/REvil.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/deprecated/RedLeaf.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/deprecated/Retefe.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/deprecated/Rozena.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/deprecated/SmallNet.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/deprecated/TSCookie.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/deprecated/TrickBot.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/deprecated/UrsnifV3.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/deprecated/_ShadowTech.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/deprecated/_VirusRat.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/deprecated/_jRat.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/deprecated/unrecom.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/deprecated/xRAT.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/malduck/LICENSE +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/malduck/README.md +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/malduck/__init__.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/malduck/test_malduck.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/mwcp/README.md +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/mwcp/__init__.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/mwcp/test_mwcp.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/utils/__init__.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/utils/aplib.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/utils/blzpack.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/utils/blzpack_lib.so +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/utils/dotnet_utils.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/utils/lznt1.py +0 -0
- {cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/utils/strings.py +0 -0
|
@@ -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
|
-
|
|
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
|
|
@@ -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
|
-
|
|
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
|
|
58
|
+
artifacts_raw.setdefault("downloads", []).append(url.decode())
|
|
59
59
|
except Exception as e:
|
|
60
60
|
print(e, sys.exc_info(), "PONY")
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
if "downloads" in artifacts_raw:
|
|
62
|
+
config.setdefault("raw", {})["downloads"] = list(set(artifacts_raw["downloads"]))
|
|
63
63
|
return config
|
|
64
64
|
|
|
65
65
|
|
|
@@ -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
|
-
|
|
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["
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
36
|
-
$config2 = {E8 [3] 00 48 8D 15 [3] 00 41 B8 ?? ?? 00 00 48 89 C1
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
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 +
|
|
142
|
+
config_rva = rva + config_data_offset
|
|
132
143
|
data = pe.get_data(config_rva, config_length)
|
|
133
144
|
off = 0
|
|
134
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
73
|
-
|
|
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
|
-
|
|
174
|
+
yara_hits = yara_scan(filebuf)
|
|
78
175
|
config = {}
|
|
79
176
|
|
|
80
|
-
for hit in
|
|
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
|
|
188
|
+
lookup_va = filebuf[decode_offset + 12: decode_offset + 16]
|
|
189
|
+
|
|
90
190
|
if not (start_offset and lookup_va):
|
|
91
|
-
|
|
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
|
|
96
|
-
data = filebuf[start_offset + 4
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
124
|
+
mutexes = list(set(mutexes))
|
|
125
|
+
cfg["mutex"] = mutexes[0] if len(mutexes) == 1 else mutexes
|
|
125
126
|
|
|
126
127
|
return cfg
|
|
127
128
|
|
|
@@ -14,17 +14,14 @@ rule SmokeLoader
|
|
|
14
14
|
{
|
|
15
15
|
meta:
|
|
16
16
|
author = "kevoreilly"
|
|
17
|
-
description = "SmokeLoader
|
|
18
|
-
cape_type = "SmokeLoader Payload"
|
|
17
|
+
description = "SmokeLoader Config Extraction"
|
|
19
18
|
strings:
|
|
20
|
-
$
|
|
21
|
-
$
|
|
22
|
-
$fetch_c2_64 = {74 ?? B? E8 03 00 00 B9 58 02 00 00 FF [5] 48 FF C? 75 F0 [6-10] 48 8D 05}
|
|
19
|
+
$fetch_c2_64_1 = {74 ?? B? E8 03 00 00 B9 58 02 00 00 FF [5] 48 (FF C?|83 EF 01) 75 (F0|EF) [6-10] 48 8D 05}
|
|
20
|
+
$fetch_c2_64_2 = {74 ?? B? E8 03 00 00 B9 58 02 00 00 FF [5] 48 (FF C?|83 EF 01) 75 (F0|EF) 33 C9 E8}
|
|
23
21
|
$fetch_c2_32 = {8B 96 [2] (00|01) 00 8B CE 5E 8B 14 95 [4] E9}
|
|
24
22
|
condition:
|
|
25
|
-
|
|
23
|
+
any of them
|
|
26
24
|
}
|
|
27
|
-
|
|
28
25
|
"""
|
|
29
26
|
|
|
30
27
|
yara_rules = yara.compile(source=rule_source)
|
|
@@ -69,7 +66,7 @@ def extract_config(filebuf):
|
|
|
69
66
|
continue
|
|
70
67
|
for item in match.strings:
|
|
71
68
|
for instance in item.instances:
|
|
72
|
-
if "$
|
|
69
|
+
if "$fetch_c2_64_1" in item.identifier:
|
|
73
70
|
match_offset = (int(instance.offset) & 0xFFFF) + instance.matched_length
|
|
74
71
|
try:
|
|
75
72
|
c2list_offset = (
|
|
@@ -78,6 +75,17 @@ def extract_config(filebuf):
|
|
|
78
75
|
except Exception:
|
|
79
76
|
break
|
|
80
77
|
delta = 8
|
|
78
|
+
if "$fetch_c2_64_2" in item.identifier:
|
|
79
|
+
match_offset = (int(instance.offset) & 0xFFFF) + instance.matched_length
|
|
80
|
+
try:
|
|
81
|
+
func = (
|
|
82
|
+
struct.unpack("<I", filebuf[match_offset : match_offset + 4])[0] + match_offset + 4
|
|
83
|
+
) & 0xFFFF
|
|
84
|
+
c2list_pointer = struct.unpack("i", filebuf[func+11:func+15])[0]+func+15
|
|
85
|
+
c2list_offset = struct.unpack("H", filebuf[c2list_pointer:c2list_pointer+2])[0]
|
|
86
|
+
except Exception:
|
|
87
|
+
break
|
|
88
|
+
delta = 8
|
|
81
89
|
if "$fetch_c2_32" in item.identifier:
|
|
82
90
|
match_offset = (int(instance[0]) & 0xFFFF) + 12
|
|
83
91
|
try:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/community/CobaltStrikeBeacon.py
RENAMED
|
File without changes
|
{cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/community/CobaltStrikeStager.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cape_parsers-0.1.56 → cape_parsers-0.1.58}/cape_parsers/CAPE/community/PhemedroneStealer.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|