diff --git a/modules/processing/parsers/CAPE/AsyncRat.py b/modules/processing/parsers/CAPE/AsyncRat.py
index 12a5bcb2a60..a498e1fdc0d 100644
--- a/modules/processing/parsers/CAPE/AsyncRat.py
+++ b/modules/processing/parsers/CAPE/AsyncRat.py
@@ -2,8 +2,10 @@
import base64
import logging
+import os
import string
import struct
+from urllib.parse import urlparse
import yara
from Cryptodome.Cipher import AES
@@ -14,6 +16,8 @@
DESCRIPTION = "AsyncRat configuration parser."
AUTHOR = "Based on work of c3rb3ru5"
+IP_REGEX = r"(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)"
+
rule_source = """
rule asyncrat {
meta:
@@ -101,17 +105,50 @@ def extract_config(filebuf):
key = base64.b64decode(get_string(data, 7))
log.debug("extracted key: " + str(key))
try:
+ family = "asyncrat"
+ hosts = decrypt_config_item_list(key, data, 2)
+ ports = decrypt_config_item_list(key, data, 1)
+ version = decrypt_config_item_printable(key, data, 3)
+ install_folder = get_wide_string(data, 5)
+ install_file = get_wide_string(data, 6)
+ install = decrypt_config_item_printable(key, data, 4)
+ mutex = decrypt_config_item_printable(key, data, 8)
+ pastebin = decrypt(key, base64.b64decode(data[12][1:])).encode("ascii").replace(b"\x0f", b"")
+
config = {
- "family": "asyncrat",
- "hosts": decrypt_config_item_list(key, data, 2),
- "ports": decrypt_config_item_list(key, data, 1),
- "version": decrypt_config_item_printable(key, data, 3),
- "install_folder": get_wide_string(data, 5),
- "install_file": get_wide_string(data, 6),
- "install": decrypt_config_item_printable(key, data, 4),
- "mutex": decrypt_config_item_printable(key, data, 8),
- "pastebin": decrypt(key, base64.b64decode(data[12][1:])).encode("ascii").replace(b"\x0f", b""),
+ "family": family,
+ "version": version,
+ "category": "rat",
+ "mutex": mutex,
+ "paths": [{"path": os.path.join(install_folder, install_file), "usage": "install" if install else "other"}],
+ "other": {
+ # No context around how these are used
+ "hosts": hosts,
+ "ports": ports,
+ },
}
+
+ if pastebin != b"null":
+ parsed_url = urlparse(pastebin).decode()
+ port = parsed_url.port
+ if not port:
+ port = 443 if parsed_url.scheme == "https" else 80
+
+ config.update(
+ {
+ "http": [
+ {
+ "uri": parsed_url.geturl(),
+ "protocol": parsed_url.scheme,
+ "hostname": parsed_url.netloc,
+ "port": port,
+ "path": parsed_url.path,
+ "method": "GET",
+ "usage": "c2",
+ }
+ ]
+ }
+ )
except Exception as e:
print(e)
return {}
diff --git a/modules/processing/parsers/CAPE/Azorult.py b/modules/processing/parsers/CAPE/Azorult.py
index aed7457f865..fc1641c35aa 100644
--- a/modules/processing/parsers/CAPE/Azorult.py
+++ b/modules/processing/parsers/CAPE/Azorult.py
@@ -54,10 +54,11 @@ def string_from_offset(data, offset):
def extract_config(filebuf):
pe = pefile.PE(data=filebuf, fast_load=False)
image_base = pe.OPTIONAL_HEADER.ImageBase
+ config = {}
ref_c2 = yara_scan(filebuf, "$ref_c2")
if ref_c2 is None:
- return
+ return config
ref_c2_offset = int(ref_c2["$ref_c2"])
@@ -71,6 +72,6 @@ def extract_config(filebuf):
c2_domain = string_from_offset(filebuf, c2_list_offset)
if c2_domain:
- return {"address": c2_domain.decode()}
+ config["tcp"] = [{"server_domain": c2_domain.decode(), "usage": "c2"}]
- return {}
+ return config
diff --git a/modules/processing/parsers/CAPE/BackOffLoader.py b/modules/processing/parsers/CAPE/BackOffLoader.py
index f62fd29461d..7c6615a530d 100644
--- a/modules/processing/parsers/CAPE/BackOffLoader.py
+++ b/modules/processing/parsers/CAPE/BackOffLoader.py
@@ -7,6 +7,8 @@
from Cryptodome.Cipher import ARC4
CFG_START = "1020304050607080"
+AUTHOR = "CAPE"
+DESCRIPTION = "BackOffLoader configuration parser."
def RC4(key, data):
@@ -16,24 +18,35 @@ def RC4(key, data):
def extract_config(data):
config_data = {}
- pe = pefile.PE(data=data)
- for section in pe.sections:
- if b".data" in section.Name:
- data = section.get_data()
- if CFG_START != hexlify(unpack_from(">8s", data, offset=8)[0]):
- return None
- rc4_seed = bytes(bytearray(unpack_from(">8B", data, offset=24)))
- key = md5(rc4_seed).digest()[:5]
- enc_data = bytes(bytearray(unpack_from(">8192B", data, offset=32)))
- dec_data = RC4(key, enc_data)
- config_data = {
- "Version": unpack_from(">5s", data, offset=16)[0],
- "RC4Seed": hexlify(rc4_seed),
- "EncryptionKey": hexlify(key),
- "OnDiskConfigKey": unpack_from("20s", data, offset=8224)[0],
- "Build": dec_data[:16].strip("\x00"),
- "URLs": [url.strip("\x00") for url in dec_data[16:].split("|")],
- }
+ try:
+ pe = pefile.PE(data=data)
+ for section in pe.sections:
+ if b".data" in section.Name:
+ data = section.get_data()
+ if CFG_START != hexlify(unpack_from(">8s", data, offset=8)[0]):
+ return None
+ rc4_seed = bytes(bytearray(unpack_from(">8B", data, offset=24)))
+ key = md5(rc4_seed).digest()[:5]
+ enc_data = bytes(bytearray(unpack_from(">8192B", data, offset=32)))
+ dec_data = RC4(key, enc_data)
+ config_data = {
+ "version": unpack_from(">5s", data, offset=16)[0],
+ "encryption": [
+ {
+ "algorithm": "RC4",
+ "key": hexlify(key),
+ "seed": hexlify(rc4_seed),
+ "binaries": [{"data": dec_data[:16].strip("\x00")}],
+ "http": [{"uri": url} for url in [url.strip("\x00") for url in dec_data[16:].split("|")]],
+ "other": {
+ "OnDiskConfigKey": unpack_from("20s", data, offset=8224)[0],
+ },
+ }
+ ],
+ }
+ except pefile.PEFormatError:
+ # This isn't a PE file, therefore unlikely to extract a configuration
+ pass
return config_data
diff --git a/modules/processing/parsers/CAPE/BackOffPOS.py b/modules/processing/parsers/CAPE/BackOffPOS.py
index c3bc9693af0..a7070ec01fc 100644
--- a/modules/processing/parsers/CAPE/BackOffPOS.py
+++ b/modules/processing/parsers/CAPE/BackOffPOS.py
@@ -7,6 +7,8 @@
from Cryptodome.Cipher import ARC4
header_ptrn = b"Content-Type: application/x-www-form-urlencoded"
+AUTHOR = "CAPE"
+DESCRIPTION = "BackOffPOS configuration parser."
def RC4(key, data):
@@ -16,25 +18,34 @@ def RC4(key, data):
def extract_config(data):
config_data = {}
- pe = pefile.PE(data=data)
- for section in pe.sections:
- if b".data" in section.Name:
- data = section.get_data()
- cfg_start = data.find(header_ptrn)
- if not cfg_start or cfg_start == -1:
- return None
- start_offset = cfg_start + len(header_ptrn) + 1
- rc4_seed = bytes(bytearray(unpack_from(">8B", data, offset=start_offset)))
- key = md5(rc4_seed).digest()[:5]
- enc_data = bytes(bytearray(unpack_from(">8192B", data, offset=start_offset + 8)))
- dec_data = RC4(key, enc_data)
- config_data = {
- "RC4Seed": hexlify(rc4_seed),
- "EncryptionKey": hexlify(key),
- "Build": dec_data[:16].strip("\x00"),
- "URLs": [url.strip("\x00") for url in dec_data[16:].split("|")],
- "Version": unpack_from(">5s", data, offset=start_offset + 16 + 8192)[0],
- }
+ try:
+ pe = pefile.PE(data=data)
+ for section in pe.sections:
+ if b".data" in section.Name:
+ data = section.get_data()
+ cfg_start = data.find(header_ptrn)
+ if not cfg_start or cfg_start == -1:
+ return None
+ start_offset = cfg_start + len(header_ptrn) + 1
+ rc4_seed = bytes(bytearray(unpack_from(">8B", data, offset=start_offset)))
+ key = md5(rc4_seed).digest()[:5]
+ enc_data = bytes(bytearray(unpack_from(">8192B", data, offset=start_offset + 8)))
+ dec_data = RC4(key, enc_data)
+ config_data = {
+ "version": unpack_from(">5s", data, offset=start_offset + 16 + 8192)[0],
+ "encryption": [
+ {
+ "algorithm": "RC4",
+ "key": hexlify(key),
+ "seed": hexlify(rc4_seed),
+ "binaries": [{"data": dec_data[:16].strip("\x00")}],
+ "http": [{"uri": url} for url in [url.strip("\x00") for url in dec_data[16:].split("|")]],
+ }
+ ],
+ }
+ except pefile.PEFormatError:
+ # This isn't a PE file, therefore unlikely to extract a configuration
+ pass
return config_data
diff --git a/modules/processing/parsers/CAPE/BitPaymer.py b/modules/processing/parsers/CAPE/BitPaymer.py
index 89be091e276..55369af2093 100644
--- a/modules/processing/parsers/CAPE/BitPaymer.py
+++ b/modules/processing/parsers/CAPE/BitPaymer.py
@@ -12,15 +12,16 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-DESCRIPTION = "BitPaymer configuration parser."
-AUTHOR = "kevoreilly"
-
import string
import pefile
import yara
from Cryptodome.Cipher import ARC4
+DESCRIPTION = "BitPaymer configuration parser."
+AUTHOR = "kevoreilly"
+
+
rule_source = """
rule BitPaymer
{
@@ -83,7 +84,7 @@ def extract_config(file_data):
for item in raw.split(b"\x00"):
data = "".join(convert_char(c) for c in item)
if len(data) == 760:
- config["RSA public key"] = data
+ config["encryption"] = [{"algorithm": "RSA", "public_key": data}]
elif len(data) > 1 and "\\x" not in data:
- config["strings"] = data
+ config["decoded_strings"] = [data]
return config
diff --git a/modules/processing/parsers/CAPE/BlackNix.py b/modules/processing/parsers/CAPE/BlackNix.py
index 56dec06c9be..f9eb3fff0ed 100644
--- a/modules/processing/parsers/CAPE/BlackNix.py
+++ b/modules/processing/parsers/CAPE/BlackNix.py
@@ -1,19 +1,19 @@
import pefile
+AUTHOR = "CAPE"
+DESCRIPTION = "BlackNix configuration parser."
+
def extract_raw_config(raw_data):
- try:
- pe = pefile.PE(data=raw_data)
- rt_string_idx = [entry.id for entry in pe.DIRECTORY_ENTRY_RESOURCE.entries].index(pefile.RESOURCE_TYPE["RT_RCDATA"])
- rt_string_directory = pe.DIRECTORY_ENTRY_RESOURCE.entries[rt_string_idx]
- for entry in rt_string_directory.directory.entries:
- if str(entry.name) == "SETTINGS":
- data_rva = entry.directory.entries[0].data.struct.OffsetToData
- size = entry.directory.entries[0].data.struct.Size
- data = pe.get_memory_mapped_image()[data_rva : data_rva + size]
- return data.split("}")
- except Exception:
- return None
+ pe = pefile.PE(data=raw_data)
+ rt_string_idx = [entry.id for entry in pe.DIRECTORY_ENTRY_RESOURCE.entries].index(pefile.RESOURCE_TYPE["RT_RCDATA"])
+ rt_string_directory = pe.DIRECTORY_ENTRY_RESOURCE.entries[rt_string_idx]
+ for entry in rt_string_directory.directory.entries:
+ if str(entry.name) == "SETTINGS":
+ data_rva = entry.directory.entries[0].data.struct.OffsetToData
+ size = entry.directory.entries[0].data.struct.Size
+ data = pe.get_memory_mapped_image()[data_rva : data_rva + size]
+ return data.split("}")
def decode(line):
@@ -28,30 +28,34 @@ def extract_config(data):
try:
config_raw = extract_raw_config(data)
if config_raw:
- return {
- "Mutex": decode(config_raw[1])[::-1],
- "Anti Sandboxie": decode(config_raw[2])[::-1],
- "Max Folder Size": decode(config_raw[3])[::-1],
- "Delay Time": decode(config_raw[4])[::-1],
- "Password": decode(config_raw[5])[::-1],
- "Kernel Mode Unhooking": decode(config_raw[6])[::-1],
- "User More Unhooking": decode(config_raw[7])[::-1],
- "Melt Server": decode(config_raw[8])[::-1],
- "Offline Screen Capture": decode(config_raw[9])[::-1],
- "Offline Keylogger": decode(config_raw[10])[::-1],
- "Copy To ADS": decode(config_raw[11])[::-1],
- "Domain": decode(config_raw[12])[::-1],
- "Persistence Thread": decode(config_raw[13])[::-1],
- "Active X Key": decode(config_raw[14])[::-1],
- "Registry Key": decode(config_raw[15])[::-1],
- "Active X Run": decode(config_raw[16])[::-1],
- "Registry Run": decode(config_raw[17])[::-1],
- "Safe Mode Startup": decode(config_raw[18])[::-1],
- "Inject winlogon.exe": decode(config_raw[19])[::-1],
- "Install Name": decode(config_raw[20])[::-1],
- "Install Path": decode(config_raw[21])[::-1],
- "Campaign Name": decode(config_raw[22])[::-1],
- "Campaign Group": decode(config_raw[23])[::-1],
+ config = {
+ "campaign_id": [config_raw["Campaign Name"], config_raw["Campaign Group"]],
+ "category": ["keylogger", "apt"],
+ "password": [config_raw["Password"]],
+ "mutex": [config_raw["Mutex"]],
+ "sleep_delay": config_raw["Delay Time"],
+ "paths": [{"path": config_raw["Install Path"], "usage": "install"}],
+ "registry": [{"key": config_raw["Registry Key"], "usage": "other"}],
+ "other": {
+ "Anti Sandboxie": config_raw["Anti Sandboxie"],
+ "Max Folder Size": config_raw["Max Folder Size"],
+ "Kernel Mode Unhooking": config_raw["Kernel Mode Unhooking"],
+ "User More Unhooking": config_raw["User More Unhooking"],
+ "Melt Server": config_raw["Melt Server"],
+ "Offline Screen Capture": config_raw["Offline Screen Capture"],
+ "Offline Keylogger": config_raw["Offline Keylogger"],
+ "Copy To ADS": config_raw["Copy To ADS"],
+ "Domain": config_raw["Domain"],
+ "Persistence Thread": config_raw["Persistence Thread"],
+ "Active X Key": config_raw["Active X Key"],
+ "Active X Run": config_raw["Active X Run"],
+ "Registry Run": config_raw["Registry Run"],
+ "Safe Mode Startup": config_raw["Safe Mode Startup"],
+ "Inject winlogon.exe": config_raw["Inject winlogon.exe"],
+ },
}
+
+ return config
+
except Exception:
- return None
+ return {}
diff --git a/modules/processing/parsers/CAPE/Blister.py b/modules/processing/parsers/CAPE/Blister.py
index 9c758117157..c3aeacb0798 100644
--- a/modules/processing/parsers/CAPE/Blister.py
+++ b/modules/processing/parsers/CAPE/Blister.py
@@ -20,6 +20,10 @@
log = logging.getLogger(__name__)
# https://github.com/Robin-Pwner/Rabbit-Cipher/
+AUTHOR = "Based on work of soolidsnake"
+DESCRIPTION = "Blister configuration parser."
+
+
def ROTL8(v, n):
return ((v << n) & 0xFF) | ((v >> (8 - n)) & 0xFF)
@@ -436,12 +440,14 @@ def main():
main()
# CAPE: Derived from decrypt_memory()
+
+
def extract_config(data):
try:
pe = pefile.PE(data=data)
except Exception:
log.info("Not a PE file")
- return -1
+ return {}
if pe.FILE_HEADER.Machine == 0x8664:
arch_size = 8
@@ -468,7 +474,7 @@ def extract_config(data):
if not key_offset or not tag_offset:
log.info("Error: signature not found")
- return -1
+ return {}
key_offset = key_offset[0].strings[0][0]
tag_offset = tag_offset[0].strings[0][0]
@@ -516,7 +522,7 @@ def extract_config(data):
key_pattern = decrypted_memory[key_pattern_offset + 12 : key_pattern_offset + 12 + 4]
else:
log.info("key_pattern_rule: Error signature not found")
- return 0
+ return {}
config_tag = (u32(key)) ^ (u32(key_pattern))
@@ -524,7 +530,7 @@ def extract_config(data):
if encrypted_config_offset == -1:
log.info("Encrypted config not found")
- return -1
+ return {}
config_size = 0x644
@@ -556,7 +562,7 @@ def extract_config(data):
elif (flag & 0x10) != 0:
injection_method = "Process hollowing IE or Werfault"
- config = {
+ config_raw = {
"Flag": hex(flag),
"Payload export hash": hex(u32(payload_export_hash)),
"Payload filename": w_payload_filename_and_cmdline,
@@ -569,4 +575,24 @@ def extract_config(data):
"Injection method": injection_method,
}
+ config = {
+ "sleep_delay": config_raw["Sleep after injection"],
+ "binaries": [
+ {
+ "datatype": "payload",
+ "other": {
+ "Payload filename": config_raw["Payload filename"],
+ "Payload export hash": config_raw["Payload export hash"],
+ },
+ }
+ ],
+ "encryptions": {"algorithm": "rabbit", "key": config_raw["Rabbit key"], "seed": config_raw["Rabbit IV"]},
+ "other": {
+ "Compressed data size": config_raw["Compressed data size"],
+ "Uncompressed data size": config_raw["Uncompressed data size"],
+ "Persistence": config_raw["Persistence"],
+ "Injection method": config_raw["Injection method"],
+ },
+ }
+
return config
diff --git a/modules/processing/parsers/CAPE/BuerLoader.py b/modules/processing/parsers/CAPE/BuerLoader.py
index b0dde9968d1..bdf706a8ee3 100644
--- a/modules/processing/parsers/CAPE/BuerLoader.py
+++ b/modules/processing/parsers/CAPE/BuerLoader.py
@@ -16,6 +16,20 @@
DESCRIPTION = "BuerLoader configuration parser."
AUTHOR = "kevoreilly"
+rule_source = """
+rule BuerLoader
+{
+ meta:
+ author = "kevoreilly & Rony (@r0ny_123)"
+ cape_type = "BuerLoader Payload"
+ strings:
+ $trap = {0F 31 89 45 ?? 6A 00 8D 45 ?? 8B CB 50 E8 [4] 0F 31}
+ $decode = {8A 0E 84 C9 74 0E 8B D0 2A 0F 46 88 0A 42 8A 0E 84 C9 75 F4 5F 5E 5D C2 04 00}
+ $op = {33 C0 85 D2 7E 1? 3B C7 7D [0-15] 40 3B C2 7C ?? EB 02}
+ condition:
+ uint16(0) == 0x5A4D and 2 of them
+}
+"""
def decrypt_string(string):
@@ -32,8 +46,10 @@ def extract_config(filebuf):
for item in data.split(b"\x00\x00"):
try:
dec = decrypt_string(item.lstrip(b"\x00").rstrip(b"\x00").decode())
+ if "dll" not in dec and " " not in dec and ";" not in dec and "." in dec:
+ cfg["other"] = {"address": dec}
except Exception:
pass
- if "dll" not in dec and " " not in dec and ";" not in dec and "." in dec:
- cfg.setdefault("address", []).append(dec)
- return cfg
+ if cfg:
+ cfg["family"] = "BuerLoader"
+ return cfg
diff --git a/modules/processing/parsers/CAPE/BumbleBee.py b/modules/processing/parsers/CAPE/BumbleBee.py
index dc166cb3d9b..6015cf205d2 100644
--- a/modules/processing/parsers/CAPE/BumbleBee.py
+++ b/modules/processing/parsers/CAPE/BumbleBee.py
@@ -128,6 +128,8 @@ def extract_config(data):
# Extract config ciphertext
config_match = regex.search(data)
+ if not config_match:
+ return cfg
campaign_id, botnet_id, c2s = extract_config_data(data, pe, config_match)
# RC4 Decrypt
diff --git a/modules/processing/parsers/CAPE/ChChes.py b/modules/processing/parsers/CAPE/ChChes.py
index f6f7619710d..508fac9db0e 100644
--- a/modules/processing/parsers/CAPE/ChChes.py
+++ b/modules/processing/parsers/CAPE/ChChes.py
@@ -11,10 +11,11 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+import yara
+
DESCRIPTION = "ChChes configuration parser."
AUTHOR = "kevoreilly"
-import yara
rule_source = """
rule ChChes
@@ -63,10 +64,7 @@ def extract_config(filebuf):
if yara_matches.get("$payload3"):
c2_offsets.append(0xE2B9)
# no c2 for type4
-
- for c2_offset in c2_offsets:
- c2_url = string_from_offset(filebuf, c2_offset)
- if c2_url:
- tmp_config.setdefault("c2_url", []).append(c2_url)
+ c2_urls = [string_from_offset(filebuf, c2_offset) for c2_offset in c2_offsets if string_from_offset(filebuf, c2_offset)]
+ tmp_config = {"http": [{"uri": url, "usage": "c2"} for url in c2_urls]}
return tmp_config
diff --git a/modules/processing/parsers/CAPE/CobaltStrikeBeacon.py b/modules/processing/parsers/CAPE/CobaltStrikeBeacon.py
index ced0988ab0d..58405f228ae 100644
--- a/modules/processing/parsers/CAPE/CobaltStrikeBeacon.py
+++ b/modules/processing/parsers/CAPE/CobaltStrikeBeacon.py
@@ -23,6 +23,40 @@
from netstruct import unpack as netunpack
log = logging.getLogger(__name__)
+AUTHOR = "Gal Kristal from SentinelOne"
+DESCRIPTION = "Parses CobaltStrike Beacon's configuration from PE file or memory dump."
+rule_source = """
+rule CobaltStrikeBeacon
+{
+ meta:
+ author = "ditekshen, enzo & Elastic"
+ description = "Cobalt Strike Beacon Payload"
+ cape_type = "CobaltStrikeBeacon Payload"
+ strings:
+ $s1 = "%%IMPORT%/%" fullword ascii
+ $s2 = "www6.%%x%%x.%%s" fullword ascii
+ $s3 = "cdn.%%x%%x.%%s" fullword ascii
+ $s4 = "api.%%x%%x.%%s" fullword ascii
+ $s5 = "%%s (admin)" fullword ascii
+ $s6 = "could not spawn %%s: %%d" fullword ascii
+ $s7 = "Could not kill %%d: %%d" fullword ascii
+ $s8 = "Could not connect to pipe (%%s): %%d" fullword ascii
+ $s9 = /%%s\\.\\d[(%%08x).]+\\.%%x%%x\\.%%s/ ascii
+ $pwsh1 = "IEX (New-Object Net.Webclient).DownloadString('http" ascii
+ $pwsh2 = "powershell -nop -exec bypass -EncodedCommand \\"%%s\\"" fullword ascii
+ $ver3a = {69 68 69 68 69 6b ?? ?? 69}
+ $ver3b = {69 69 69 69}
+ $ver4a = {2e 2f 2e 2f 2e 2c ?? ?? 2e}
+ $ver4b = {2e 2e 2e 2e}
+ $a1 = "%%02d/%%02d/%%02d %%02d:%%02d:%%02d" xor(0x00-0xff)
+ $a2 = "Started service %%s on %%s" xor(0x00-0xff)
+ $a3 = "%%s as %%s\\\\%%s: %%d" xor(0x00-0xff)
+ $b_x64 = {4C 8B 53 08 45 8B 0A 45 8B 5A 04 4D 8D 52 08 45 85 C9 75 05 45 85 DB 74 33 45 3B CB 73 E6 49 8B F9 4C 8B 03}
+ $b_x86 = {8B 46 04 8B 08 8B 50 04 83 C0 08 89 55 08 89 45 0C 85 C9 75 04 85 D2 74 23 3B CA 73 E6 8B 06 8D 3C 08 33 D2}
+ condition:
+ all of ($ver3*) or all of ($ver4*) or 2 of ($a*) or any of ($b*) or 5 of ($s*) or (all of ($pwsh*) and 2 of ($s*)) or (#s9 > 6 and 4 of them)
+}
+"""
COLUMN_WIDTH = 35
SUPPORTED_VERSIONS = (3, 4)
@@ -110,7 +144,7 @@ def binary_repr(self):
def pretty_repr(self, full_config_data):
data_offset = full_config_data.find(self.binary_repr())
if data_offset < 0:
- return "Not Found"
+ return None
repr_len = len(self.binary_repr())
conf_data = full_config_data[data_offset + repr_len : data_offset + repr_len + self.length]
@@ -343,6 +377,10 @@ def _parse_config(self, version, quiet=False, as_json=False):
for conf_name, packed_conf in settings:
parsed_setting = packed_conf.pretty_repr(full_config_data)
+ if parsed_setting == None:
+ # Nothing of value
+ continue
+
if as_json:
parsed_config[conf_name] = parsed_setting
continue
@@ -443,9 +481,69 @@ def parse_encrypted_config(self, version=None, quiet=False, as_json=False):
print(json.dumps(parsed_config, cls=Base64Encoder))
+def beacon_settings_to_maco(output: dict):
+ if not output:
+ return
+ config = {"family": "Cobalt StrikeBeacon"}
+
+ # SSH details
+ ssh = {
+ "hostname": output.pop("SSH_Host", None),
+ "port": output.pop("SSH_Port", None),
+ "username": output.pop("SSH_Username", None),
+ "password": output.pop("SSH_Password_Plaintext", None),
+ "public_key": output.pop("SSH_Password_Pubkey", None),
+ "usage": "c2",
+ }
+ [ssh.pop(k) for k in list(ssh.keys()) if not ssh[k]]
+ if len(ssh.keys()) > 1:
+ config["ssh"] = [ssh]
+
+ # HTTP details
+ http = []
+ c2_domain, c2_get_path = output.pop("C2Server", ",").split(",")
+ c2_post_path = output.pop("HttpPostUri", None)
+ if c2_domain:
+ protocol = output.get("BeaconType")[0]
+ if protocol in ["HTTPS", "HTTP"]:
+ port = output.pop("Port", None)
+ if not port:
+ port = 443 if protocol == "HTTPS" else 80
+ user_agent = output.pop("UserAgent", None)
+ http_get = {
+ "uri": f"{protocol.lower()}://{c2_domain}{c2_get_path}",
+ "protocol": protocol.lower(),
+ "hostname": c2_domain,
+ "port": port,
+ "path": c2_get_path,
+ "method": output.pop("HttpGet_Verb", "GET"),
+ }
+ http_get.update({"user_agent": user_agent}) if user_agent else None
+ http.append(http_get)
+ if c2_post_path:
+ http_post = {
+ "uri": f"{protocol.lower()}://{c2_domain}{c2_get_path}",
+ "protocol": protocol.lower(),
+ "hostname": c2_domain,
+ "port": port,
+ "path": c2_post_path,
+ "method": output.pop("HttpPost_Verb", "POST"),
+ }
+ http_post.update({"user_agent": user_agent}) if user_agent else None
+ http.append(http_post)
+ config["http"] = http
+
+ config.update({"pipe": [output.pop("PipeName")]}) if output.get("PipeName") else None
+ config.update({"sleep_delay": output.pop("SleepTime")}) if output.get("SleepTime") else None
+ # Other
+ config["other"] = output
+ return config
+
+
# CAPE
def extract_config(data):
output = cobaltstrikeConfig(data).parse_config(quiet=False, as_json=True)
if output is None:
output = cobaltstrikeConfig(data).parse_encrypted_config(quiet=False, as_json=True)
- return output
+
+ return beacon_settings_to_maco(output)
diff --git a/modules/processing/parsers/CAPE/DoppelPaymer.py b/modules/processing/parsers/CAPE/DoppelPaymer.py
index e70da9d0299..cb8b7939594 100644
--- a/modules/processing/parsers/CAPE/DoppelPaymer.py
+++ b/modules/processing/parsers/CAPE/DoppelPaymer.py
@@ -12,15 +12,15 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-DESCRIPTION = "DoppelPaymer configuration parser."
-AUTHOR = "kevoreilly"
-
import string
import pefile
-import yara
from Cryptodome.Cipher import ARC4
+DESCRIPTION = "DoppelPaymer configuration parser."
+AUTHOR = "kevoreilly"
+
+
rule_source = """
rule DoppelPaymer
{
@@ -63,7 +63,9 @@ def extract_rdata(pe):
def extract_config(filebuf):
pe = pefile.PE(data=filebuf, fast_load=False)
- config = {}
+ config = {
+ "family": "DoppelPaymer",
+ }
blobs = filter(None, [x.strip(b"\x00\x00\x00\x00") for x in extract_rdata(pe).split(b"\x00\x00\x00\x00")])
for blob in blobs:
if len(blob) < LEN_BLOB_KEY:
@@ -74,7 +76,8 @@ def extract_config(filebuf):
for item in raw.split(b"\x00"):
data = "".join(convert_char(c) for c in item)
if len(data) == 406:
- config["RSA public key"] = data
+ config["encryption"] = [{"algorithm": "RSA", "public_key": data, "usage": "ransom"}]
elif len(data) > 1 and "\\x" not in data:
- config["strings"] = data
+ config.setdefault("decoded_strings", [])
+ config["decoded_strings"].append(data)
return config
diff --git a/modules/processing/parsers/CAPE/DridexLoader.py b/modules/processing/parsers/CAPE/DridexLoader.py
index 3a0ee1e0553..1642d9186de 100644
--- a/modules/processing/parsers/CAPE/DridexLoader.py
+++ b/modules/processing/parsers/CAPE/DridexLoader.py
@@ -65,7 +65,7 @@ def extract_rdata(pe):
def extract_config(filebuf):
- cfg = {}
+ cfg = {"family": "Dridex"}
pe = pefile.PE(data=filebuf, fast_load=False)
image_base = pe.OPTIONAL_HEADER.ImageBase
line, c2va_offset, delta = 0, 0, 0
@@ -121,7 +121,8 @@ def extract_config(filebuf):
port = str(struct.unpack("H", filebuf[c2_offset + 4 : c2_offset + 6])[0])
if c2_address and port:
- cfg.setdefault("address", []).append(f"{c2_address}:{port}")
+ cfg.setdefault("tcp", [])
+ cfg.append({"server_ip": c2_address, "server_port": port, "usage": "c2"})
c2_offset += 6 + delta
@@ -144,14 +145,14 @@ def extract_config(filebuf):
)
for item in raw.split(b"\x00"):
if len(item) == LEN_BLOB_KEY - 1:
- cfg["RC4 key"] = item.split(b";", 1)[0].decode()
+ cfg["encryption"] = [{"algorithm": "RSA", "public_key": item.split(b";", 1)[0].decode(), "usage": "ransom"}]
if botnet_code:
botnet_rva = struct.unpack("i", filebuf[botnet_code + 23 : botnet_code + 27])[0] - image_base
if botnet_rva:
botnet_offset = pe.get_offset_from_rva(botnet_rva)
botnet_id = struct.unpack("H", filebuf[botnet_offset : botnet_offset + 2])[0]
- cfg["Botnet ID"] = str(botnet_id)
+ cfg["other"] = {"Botnet ID": str(botnet_id)} # Might fall under identifier?
return cfg
diff --git a/modules/processing/parsers/CAPE/Emotet.py b/modules/processing/parsers/CAPE/Emotet.py
index 8e8388dea00..96bc5d43b0a 100644
--- a/modules/processing/parsers/CAPE/Emotet.py
+++ b/modules/processing/parsers/CAPE/Emotet.py
@@ -27,6 +27,7 @@
log.setLevel(logging.INFO)
AUTHOR = "kevoreilly"
+DESCRIPTION = "Emotet configuration parser."
rule_source = """
rule Emotet
@@ -186,7 +187,7 @@ def extract_emotet_rsakey(pe):
def extract_config(filebuf):
- conf_dict = {}
+ conf_dict = {"family": "Emotet", "tcp": []}
pe = None
try:
pe = pefile.PE(data=filebuf, fast_load=False)
@@ -223,7 +224,7 @@ def extract_config(filebuf):
port = str(struct.unpack("H", filebuf[c2_list_offset + 4 : c2_list_offset + 6])[0])
if not c2_address or not port:
return
- conf_dict.setdefault("address", []).append(f"{c2_address}:{port}")
+ conf_dict["tcp"].append({"server_ip": c2_address, "server_port": port, "usage": "c2"})
c2_list_offset += 8
elif yara_matches.get("$snippet4"):
c2list_va_offset = int(yara_matches["$snippet4"])
@@ -244,7 +245,7 @@ def extract_config(filebuf):
port = str(struct.unpack("H", filebuf[c2_list_offset + 4 : c2_list_offset + 6])[0])
if not c2_address or not port:
return
- conf_dict.setdefault("address", []).append(f"{c2_address}:{port}")
+ conf_dict["tcp"].append({"server_ip": c2_address, "server_port": port, "usage": "c2"})
c2_list_offset += 8
elif any(
yara_matches.get(name, False)
@@ -297,7 +298,7 @@ def extract_config(filebuf):
port = str(struct.unpack("H", filebuf[c2_list_offset + 4 : c2_list_offset + 6])[0])
if not c2_address or not port:
break
- conf_dict.setdefault("address", []).append(f"{c2_address}:{port}")
+ conf_dict["tcp"].append({"server_ip": c2_address, "server_port": port, "usage": "c2"})
c2found = True
c2_list_offset += 8
elif yara_matches.get("$snippet6"):
@@ -323,7 +324,7 @@ def extract_config(filebuf):
port = str(struct.unpack("H", filebuf[c2_list_offset + 4 : c2_list_offset + 6])[0])
if not c2_address or not port:
break
- conf_dict.setdefault("address", []).append(f"{c2_address}:{port}")
+ conf_dict["tcp"].append({"server_ip": c2_address, "server_port": port, "usage": "c2"})
c2found = True
c2_list_offset += 8
elif yara_matches.get("$snippet7"):
@@ -349,7 +350,7 @@ def extract_config(filebuf):
port = str(struct.unpack("H", filebuf[c2_list_offset + 4 : c2_list_offset + 6])[0])
if not c2_address or not port:
break
- conf_dict.setdefault("address", []).append(f"{c2_address}:{port}")
+ conf_dict["tcp"].append({"server_ip": c2_address, "server_port": port, "usage": "c2"})
c2found = True
c2_list_offset += 8
elif yara_matches.get("$snippetA"):
@@ -371,7 +372,7 @@ def extract_config(filebuf):
port = str(struct.unpack("H", filebuf[c2_list_offset + 4 : c2_list_offset + 6])[0])
if not c2_address or not port:
break
- conf_dict.setdefault("address", []).append(f"{c2_address}:{port}")
+ conf_dict["tcp"].append({"server_ip": c2_address, "server_port": port, "usage": "c2"})
c2found = True
c2_list_offset += 8
elif yara_matches.get("$snippetD"):
@@ -472,7 +473,7 @@ def extract_config(filebuf):
port = str(struct.unpack(">H", c2_list[offset + 4 : offset + 6])[0])
if not c2_address or not port:
break
- conf_dict.setdefault("address", []).append(f"{c2_address}:{port}")
+ conf_dict["tcp"].append({"server_ip": c2_address, "server_port": port, "usage": "c2"})
c2found = True
offset += 8
@@ -485,7 +486,7 @@ def extract_config(filebuf):
log.error(e)
if pem_key:
# self.reporter.add_metadata("other", {"RSA public key": pem_key.exportKey().decode()})
- conf_dict.setdefault("RSA public key", pem_key.exportKey().decode())
+ conf_dict.setdefault("encryption", []).append({"algorithm": "RSA", "public_key": pem_key.exportKey().decode()})
else:
if yara_matches.get("$ref_rsa"):
ref_rsa_offset = int(yara_matches["$ref_rsa"])
@@ -518,7 +519,9 @@ def extract_config(filebuf):
seq = asn1.DerSequence()
seq.decode(rsa_key)
# self.reporter.add_metadata("other", {"RSA public key": RSA.construct((seq[0], seq[1])).exportKey()})
- conf_dict.setdefault("RSA public key", RSA.construct((seq[0], seq[1])).exportKey())
+ conf_dict.setdefault("encryption", []).append(
+ {"algorithm": "RSA", "public_key": RSA.construct((seq[0], seq[1])).exportKey()}
+ )
else:
ref_ecc_offset = 0
delta1 = 0
@@ -645,26 +648,32 @@ def extract_config(filebuf):
eck_offset += 8
eck_key = xor_data(filebuf[eck_offset : eck_offset + size], key)
key_len = struct.unpack(".
+import yara
+
DESCRIPTION = "Enfal configuration parser."
AUTHOR = "kevoreilly"
-import yara
rule_source = """
rule Enfal
@@ -58,17 +59,20 @@ def list_from_offset(data, offset):
def extract_config(filebuf):
config = yara_scan(filebuf, "$config")
- return_conf = {}
+ return_conf = {"family": "Enfal"}
if config:
yara_offset = int(config["$config"])
+ http = dict()
c2_address = string_from_offset(filebuf, yara_offset + 0x2E8)
if c2_address:
- return_conf["c2_address"] = c2_address
+ # Based on other extractors, this is the domain?
+ http["hostname"] = c2_address
+ # Assuming c2_url is related to c2_address
c2_url = string_from_offset(filebuf, yara_offset + 0xE8)
if c2_url:
- return_conf["c2_url"] = c2_url
+ http["uri"] = c2_url
if filebuf[yara_offset + 0x13B0 : yara_offset + 0x13B1] == "S":
registrypath = string_from_offset(filebuf, yara_offset + 0x13B0)
@@ -80,7 +84,7 @@ def extract_config(filebuf):
registrypath = ""
if registrypath:
- return_conf["registrypath"] = registrypath
+ return_conf["registry"] = [{"key": registrypath, "usage": "c2"}]
if filebuf[yara_offset + 0x14A2 : yara_offset + 0x14A3] == "C":
servicename = ""
@@ -100,7 +104,11 @@ def extract_config(filebuf):
filepaths = []
if servicename:
- return_conf["servicename"] = servicename
+ return_conf["service"] = [{"name": servicename}]
if filepaths:
for path in filepaths:
- return_conf.setdefault("filepath", []).append(path)
+ return_conf.setdefault("paths", []).append({"path": path, "usage": "c2"})
+ if http:
+ return_conf["http"] = [http]
+
+ return return_conf
diff --git a/modules/processing/parsers/CAPE/EvilGrab.py b/modules/processing/parsers/CAPE/EvilGrab.py
index 2e4cae619dc..beace6fc34f 100644
--- a/modules/processing/parsers/CAPE/EvilGrab.py
+++ b/modules/processing/parsers/CAPE/EvilGrab.py
@@ -12,14 +12,15 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-DESCRIPTION = "EvilGrab configuration parser."
-AUTHOR = "kevoreilly"
-
import struct
import pefile
import yara
+DESCRIPTION = "EvilGrab configuration parser."
+AUTHOR = "kevoreilly"
+
+
rule_source = """
rule EvilGrab
{
@@ -80,31 +81,34 @@ def extract_config(filebuf):
pe = pefile.PE(data=filebuf, fast_load=False)
# image_base = pe.OPTIONAL_HEADER.ImageBase
yara_matches = yara_scan(filebuf)
- end_config = {}
+ end_config = {"family": "EvilGrab"}
for key, values in map_offset.keys():
if not yara_matches.get(key):
continue
yara_offset = int(yara_matches[key])
+ c2_tcp = dict()
c2_address = string_from_va(pe, yara_offset + values[0])
if c2_address:
- end_config["c2_address"] = c2_address
+ c2_tcp["server_ip"] = c2_address
port = str(struct.unpack("h", filebuf[yara_offset + values[1] : yara_offset + values[1] + 2])[0])
if port:
- end_config["port"] = [port, "tcp"]
+ c2_tcp["server_port"] = port
missionid = string_from_va(pe, yara_offset + values[3])
if missionid:
- end_config["missionid"] = missionid
+ end_config.setdefault("campaign_id", []).append(missionid)
version = string_from_va(pe, yara_offset + values[4])
if version:
end_config["version"] = version
injectionprocess = string_from_va(pe, yara_offset + values[5])
if injectionprocess:
- end_config["injectionprocess"] = injectionprocess
+ end_config.setdefault("inject_exe", []).append(injectionprocess)
if key != "$configure3":
mutex = string_from_va(pe, yara_offset - values[6])
if mutex:
- end_config["mutex"] = mutex
+ end_config.setdefault("mutex", []).append(mutex)
+ if c2_tcp:
+ end_config.setdefault("tcp", []).append(c2_tcp)
return end_config
diff --git a/modules/processing/parsers/CAPE/Fareit.py b/modules/processing/parsers/CAPE/Fareit.py
index 93bc8023e6c..a8f6b67223e 100644
--- a/modules/processing/parsers/CAPE/Fareit.py
+++ b/modules/processing/parsers/CAPE/Fareit.py
@@ -1,7 +1,10 @@
import re
import sys
-"""
+AUTHOR = "CAPE"
+DESCRIPTION = "Fareit configuration parser."
+
+rule_source = """
rule pony {
meta:
author = "adam"
@@ -35,10 +38,7 @@ def extract_config(memdump_path, read=False):
if buf and len(buf[0]) > 200:
cData = buf[0][200:]
"""
- artifacts_raw = {
- "controllers": [],
- "downloads": [],
- }
+ controllers, downloads = set(), set()
start = F.find(b"YUIPWDFILE0YUIPKDFILE0YUICRYPTED0YUI1.0")
if start:
@@ -54,14 +54,18 @@ def extract_config(memdump_path, read=False):
if url is None:
continue
if gate_url.match(url):
- artifacts_raw["controllers"].append(url.lower().decode())
+ controllers.add(url.lower().decode())
elif exe_url.match(url) or dll_url.match(url):
- artifacts_raw["downloads"].append(url.lower().decode())
+ downloads.add(url.lower().decode())
except Exception as e:
print(e, sys.exc_info(), "PONY")
- artifacts_raw["controllers"] = list(set(artifacts_raw["controllers"]))
- artifacts_raw["downloads"] = list(set(artifacts_raw["downloads"]))
- return artifacts_raw if len(artifacts_raw["controllers"]) != 0 or len(artifacts_raw["downloads"]) != 0 else False
+
+ config = {
+ "family": "Fareit",
+ "http": [{"uri": c, "usage": "c2"} for c in controllers] + [{"uri": d, "usage": "download"} for d in downloads],
+ }
+
+ return config
if __name__ == "__main__":
diff --git a/modules/processing/parsers/CAPE/Greame.py b/modules/processing/parsers/CAPE/Greame.py
index 0a7aeb1c128..7610b3f42cd 100644
--- a/modules/processing/parsers/CAPE/Greame.py
+++ b/modules/processing/parsers/CAPE/Greame.py
@@ -1,3 +1,4 @@
+import os
import string
import pefile
@@ -27,61 +28,67 @@ def xor_decode(data):
def parse_config(raw_config):
+ config = {"family": "Greame"}
if len(raw_config) <= 20:
return None
- domains = ""
- ports = ""
# Config sections 0 - 19 contain a list of Domains and Ports
for x in range(19):
if len(raw_config[x]) > 1:
- domains += xor_decode(raw_config[x]).split(":", 1)[0]
- domains += "|"
- ports += xor_decode(raw_config[x]).split(":", 2)[1]
- ports += "|"
- config_dict = {
- "Domain": domains[:-1],
- "Port": ports[:-1],
- "ServerID": xor_decode(raw_config[20]),
- "Password": xor_decode(raw_config[21]),
+ domain, port = xor_decode(raw_config[x]).split(":", 2)
+ config.setdefault("tcp", []).append({"server_domain": domain, "server_port": port})
+ config["identifier"] = xor_decode(raw_config[20]) # Server ID
+ config["passwords"] = [xor_decode(raw_config[n]) for n in [21, 73]] # Password, Google Chrome Passwords
+ config["ftp"] = (
+ [
+ {
+ "username": xor_decode(raw_config[41]),
+ "password": xor_decode(raw_config[42]),
+ "hostname": xor_decode(raw_config[38]),
+ "port": xor_decode(raw_config[43]),
+ "path": xor_decode(raw_config[39]),
+ }
+ ],
+ )
+ config["paths"] = [{"path": os.path.join(xor_decode(raw_config[25]), xor_decode(raw_config[26])), "usage": "install"}]
+ config["mutex"] = [xor_decode(raw_config[62])]
+ config["registry"] = [
+ {"key": xor_decode(raw_config[28])}, # "REG Key HKLM"
+ {"key": xor_decode(raw_config[29])}, # "REG Key HKLU"
+ ]
+ config["binary"] = [
+ {"data": xor_decode(raw_config[31])}, # "Message Box Icon"
+ {"data": xor_decode(raw_config[32])}, # "Message Box Button"
+ ]
+
+ # Below sound like capabilities but unsure of values..
+ config["other"] = {
"Install Flag": xor_decode(raw_config[22]),
- "Install Directory": xor_decode(raw_config[25]),
- "Install File Name": xor_decode(raw_config[26]),
"Active X Startup": xor_decode(raw_config[27]),
- "REG Key HKLM": xor_decode(raw_config[28]),
- "REG Key HKCU": xor_decode(raw_config[29]),
"Enable Message Box": xor_decode(raw_config[30]),
- "Message Box Icon": xor_decode(raw_config[31]),
- "Message Box Button": xor_decode(raw_config[32]),
"Install Message Title": xor_decode(raw_config[33]),
"Install Message Box": xor_decode(raw_config[34]).replace("\r\n", " "),
"Activate Keylogger": xor_decode(raw_config[35]),
"Keylogger Backspace = Delete": xor_decode(raw_config[36]),
"Keylogger Enable FTP": xor_decode(raw_config[37]),
- "FTP Address": xor_decode(raw_config[38]),
- "FTP Directory": xor_decode(raw_config[39]),
- "FTP UserName": xor_decode(raw_config[41]),
- "FTP Password": xor_decode(raw_config[42]),
- "FTP Port": xor_decode(raw_config[43]),
"FTP Interval": xor_decode(raw_config[44]),
"Persistance": xor_decode(raw_config[59]),
"Hide File": xor_decode(raw_config[60]),
"Change Creation Date": xor_decode(raw_config[61]),
- "Mutex": xor_decode(raw_config[62]),
"Melt File": xor_decode(raw_config[63]),
"Startup Policies": xor_decode(raw_config[69]),
"USB Spread": xor_decode(raw_config[70]),
"P2P Spread": xor_decode(raw_config[71]),
- "Google Chrome Passwords": xor_decode(raw_config[73]),
}
if xor_decode(raw_config[57]) == 0:
- config_dict["Process Injection"] = "Disabled"
+ config["other"]["Process Injection"] = "Disabled"
elif xor_decode(raw_config[57]) == 1:
- config_dict["Process Injection"] = "Default Browser"
+ config["other"]["Process Injection"] = "Default Browser"
elif xor_decode(raw_config[57]) == 2:
- config_dict["Process Injection"] = xor_decode(raw_config[58])
+ config["other"]["Process Injection"] = xor_decode(raw_config[58])
else:
- config_dict["Process Injection"] = "None"
- return config_dict
+ config["other"]["Process Injection"] = "None"
+
+ return config
def extract_config(data):
diff --git a/modules/processing/parsers/CAPE/GuLoader.py b/modules/processing/parsers/CAPE/GuLoader.py
index 1d858ac7d6d..4063a136757 100644
--- a/modules/processing/parsers/CAPE/GuLoader.py
+++ b/modules/processing/parsers/CAPE/GuLoader.py
@@ -5,12 +5,33 @@
url_regex = re.compile(rb"http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+")
+DESCRIPTION = "GuLoader config extractor."
+AUTHOR = "CAPE"
+rule_source = """
+rule GuLoader
+{
+ meta:
+ author = "kevoreilly"
+ description = "Shellcode injector and downloader"
+ cape_type = "GuLoader Payload"
+ strings:
+ $trap0 = {0F 85 [2] FF FF 81 BD ?? 00 00 00 [2] 00 00 0F 8F [2] FF FF 39 D2 83 FF 00}
+ $trap1 = {49 83 F9 00 75 [1-20] 83 FF 00 [2-6] 81 FF}
+ $trap2 = {39 CB 59 01 D7 49 85 C8 83 F9 00 75 B3}
+ $trap3 = {61 0F AE E8 0F 31 0F AE E8 C1 E2 20 09 C2 29 F2 83 FA 00 7E CE C3}
+ $antihook = {FF 34 08 [0-48] 8F 04 0B [0-80] 83 C1 04 83 F9 18 75 [0-128] FF E3}
+ $cape_string = "cape_options"
+ condition:
+ 2 of them and not $cape_string
+}
+"""
+
def extract_config(data):
try:
urls = [url.lower().decode() for url in url_regex.findall(data)]
if urls:
- return {"URLs": urls}
+ return {"family": "GuLoader", "http": [{"uri": uri, "usage": "download"} for uri in urls]}
except Exception as e:
print(e)
diff --git a/modules/processing/parsers/CAPE/Hancitor.py b/modules/processing/parsers/CAPE/Hancitor.py
index b1b6560490f..d1dca0a26bc 100644
--- a/modules/processing/parsers/CAPE/Hancitor.py
+++ b/modules/processing/parsers/CAPE/Hancitor.py
@@ -24,13 +24,15 @@ def extract_config(filebuf):
ENCRYPT_DATA = DATA_SECTION[24:2000]
DECRYPTED_DATA = ARC4.new(key).decrypt(ENCRYPT_DATA)
build_id, controllers = list(filter(None, DECRYPTED_DATA.split(b"\x00")))
- cfg.setdefault("Build ID", build_id.decode())
+ cfg.setdefault("version", build_id.decode())
controllers = list(filter(None, controllers.split(b"|")))
if controllers:
- cfg.setdefault("address", [url.decode() for url in controllers])
+ cfg.setdefault("http", []).extend([{"uri": url.decode(), "usage": "c2"} for url in controllers])
except Exception as e:
log.warning(e)
+ if cfg:
+ cfg["family"] = "Hancitor"
return cfg
diff --git a/modules/processing/parsers/CAPE/HttpBrowser.py b/modules/processing/parsers/CAPE/HttpBrowser.py
index 293ef590b7e..3fbcb9abc58 100644
--- a/modules/processing/parsers/CAPE/HttpBrowser.py
+++ b/modules/processing/parsers/CAPE/HttpBrowser.py
@@ -12,15 +12,15 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-DESCRIPTION = "HttpBrowser configuration parser."
-AUTHOR = "kevoreilly"
-
-
import struct
import pefile
import yara
+DESCRIPTION = "HttpBrowser configuration parser."
+AUTHOR = "kevoreilly"
+
+
rule_source = """
rule HttpBrowser
{
@@ -90,35 +90,42 @@ def extract_config(filebuf):
# image_base = pe.OPTIONAL_HEADER.ImageBase
yara_matches = yara_scan(filebuf)
- tmp_config = {}
+ tmp_config = {"family": "HTTPBrowser"}
+ tcp_connections = []
for key, values in match_map.keys():
if yara_matches.get(key):
yara_offset = int(yara_matches[key])
-
if key in ("$connect_1", "$connect_2", "$connect_3"):
port = ascii_from_va(pe, yara_offset + values[0])
- if port:
- tmp_config["port"] = [port, "tcp"]
c2_address = unicode_from_va(pe, yara_offset + values[1])
if c2_address:
- tmp_config.setdefault("c2_address", []).append(c2_address)
+ tcp_conn = {"server_ip": c2_address, "usage": "c2"}
+ if port:
+ tcp_conn["server_port"] = port
+ tcp_connections.append(tcp_conn)
if key == "$connect_3":
c2_address = unicode_from_va(pe, yara_offset + values[2])
if c2_address:
- tmp_config.setdefault("c2_address", []).append(c2_address)
+ tcp_conn = {"server_ip": c2_address, "usage": "c2"}
+ if port:
+ tcp_conn["server_port"] = port
+ tcp_connections.append(tcp_conn)
else:
c2_address = unicode_from_va(pe, yara_offset + values[0])
if c2_address:
- tmp_config["c2_address"] = c2_address
+ tcp_connections.append({"server_ip": c2_address, "usage": "c2"})
filepath = unicode_from_va(pe, yara_offset + values[1])
if filepath:
- tmp_config["filepath"] = filepath
+ tmp_config["paths"] = [{"path": filepath, "usage": "c2"}]
injectionprocess = unicode_from_va(pe, yara_offset - values[2])
if injectionprocess:
- tmp_config["injectionprocess"] = injectionprocess
+ tmp_config["inject_exe"] = [injectionprocess]
+
+ if tcp_connections:
+ tmp_config["tcp"] = tcp_connections
return tmp_config
diff --git a/modules/processing/parsers/CAPE/IcedID.py b/modules/processing/parsers/CAPE/IcedID.py
index d041acc8b95..d6e3ba1cf3e 100644
--- a/modules/processing/parsers/CAPE/IcedID.py
+++ b/modules/processing/parsers/CAPE/IcedID.py
@@ -60,10 +60,13 @@ def extract_config(filebuf):
decrypted_data = ARC4.new(key).decrypt(enc_config)
config = list(filter(None, decrypted_data.split(b"\x00")))
return {
- "Bot ID": str(struct.unpack("I", decrypted_data[:4])[0]),
- "Minor Version": str(struct.unpack("I", decrypted_data[4:8])[0]),
- "Path": config[1],
- "address": [controller[1:] for controller in config[2:]],
+ "family": "IcedID",
+ "version": str(struct.unpack("I", decrypted_data[4:8])[0]),
+ "paths": [{"path": config[1], "usage": "other"}],
+ "http": [{"uri": controller[1:]} for controller in config[2:]],
+ "other": {
+ "Bot ID": str(struct.unpack("I", decrypted_data[:4])[0]),
+ },
}
except Exception as e:
log.error("Error: %s", e)
diff --git a/modules/processing/parsers/CAPE/IcedIDLoader.py b/modules/processing/parsers/CAPE/IcedIDLoader.py
index b0cf2288ff1..bfe628b98c6 100644
--- a/modules/processing/parsers/CAPE/IcedIDLoader.py
+++ b/modules/processing/parsers/CAPE/IcedIDLoader.py
@@ -12,7 +12,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-import os
import struct
import pefile
@@ -37,8 +36,9 @@ def extract_config(filebuf):
if n > 32:
break
campaign, c2 = struct.unpack("I30s", bytes(dec))
- cfg["C2"] = c2.split(b"\00", 1)[0].decode()
- cfg["Campaign"] = campaign
+ cfg["family"] = "IcedIDLoader"
+ cfg["tcp"] = [{"server_domain": c2.split(b"\00", 1)[0].decode(), "usage": "c2"}]
+ cfg["campaign_id"] = campaign
return cfg
diff --git a/modules/processing/parsers/CAPE/LokiBot.py b/modules/processing/parsers/CAPE/LokiBot.py
index 20fed82cfe8..a28e9411b23 100644
--- a/modules/processing/parsers/CAPE/LokiBot.py
+++ b/modules/processing/parsers/CAPE/LokiBot.py
@@ -30,6 +30,20 @@
DESCRIPTION = "LokiBot configuration parser."
AUTHOR = "sysopfb"
+rule_source = """
+rule LokiBot
+{
+ meta:
+ author = "kevoreilly"
+ description = "LokiBot Payload"
+ cape_type = "LokiBot Payload"
+ strings:
+ $a1 = "DlRycq1tP2vSeaogj5bEUFzQiHT9dmKCn6uf7xsOY0hpwr43VINX8JGBAkLMZW"
+ $a2 = "last_compatible_version"
+ condition:
+ uint16(0) == 0x5A4D and (all of ($a*))
+}
+"""
def find_iv(img):
@@ -41,54 +55,43 @@ def find_iv(img):
return iv
-def find_conf(img):
- ret = []
-
- num_addr_re1 = re.compile(
- rb"""
- \x6A(?P.) # 6A 08 push 8
- \x59 # 59 pop ecx
- \xBE(?P.{4}) # BE D0 88 41 00 mov esi, offset encrypted_data1
- \x8D\xBD.{4} # 8D BD 68 FE FF FF lea edi, [ebp+encrypted_data_list]
- \xF3\xA5 # F3 A5 rep movsd
- \x6A. # 6A 43 push 43h ; 'C'
- \x5B # 5B pop ebx
- \x53 # 53 push ebx
- \x8D\x85.{4} # 8D 85 89 FE FF FF lea eax, [ebp+var_177]
- \xA4 # A4 movsb
- \x6A\x00 # 6A 00 push 0
- \x50 # 50 push eax
- \xE8.{4} # E8 78 E9 FE FF call about_memset
- """,
- re.DOTALL | re.VERBOSE,
- )
- num_addr_re2 = re.compile(
- rb"""
- \x6A(?P.) # 6A 08 push 8
- \x59 # 59 pop ecx
- \xBE(?P.{4}) # BE F4 88 41 00 mov esi, offset encrypted_data2
- \x8D.{2,5} # 8D BD CC FE FF FF lea edi, [ebp+var_134]
- \xF3\xA5 # F3 A5 rep movsd
- \x53 # 53 push ebx
- \x8D.{2,5} # 8D 85 ED FE FF FF lea eax, [ebp+var_113]
- \x6A\x00 # 6A 00 push 0
- \x50 # 50 push eax
- \xA4 # A4 movsb
- \xE8.{4} # E8 58 E9 FE FF call about_memset
- """,
- re.DOTALL | re.VERBOSE,
- )
-
- num_addr_list = re.findall(num_addr_re1, img)
- num_addr_list.extend(re.findall(num_addr_re2, img))
-
- for num, addr in num_addr_list:
- dlen = ord(num) * 4
- (addr,) = struct.unpack_from(".
-DESCRIPTION = "RCSession configuration parser."
-AUTHOR = "kevoreilly"
-
import struct
import pefile
import yara
+DESCRIPTION = "RCSession configuration parser."
+AUTHOR = "kevoreilly"
+
+
rule_source = """
rule RCSession
{
@@ -93,29 +94,33 @@ def extract_config(filebuf):
config_offset = pe.get_offset_from_rva(config_rva)
size = struct.unpack("i", filebuf[yara_offset + 88 : yara_offset + 92])[0]
key = struct.unpack("i", filebuf[config_offset + 128 : config_offset + 132])[0]
- end_config = {}
+ end_config = {"family": "RCSession"}
tmp_config = decode(filebuf[config_offset : config_offset + size], size, key)
c2_address = str(tmp_config[156 : 156 + MAX_IP_STRING_SIZE])
if c2_address:
- end_config.setdefault("c2_address", []).append(c2_address)
+ end_config.setdefault("tcp", []).append({"server_ip": c2_address, "usage": "c2"})
c2_address = str(tmp_config[224 : 224 + MAX_IP_STRING_SIZE])
if c2_address:
- end_config.setdefault("c2_address", []).append(c2_address)
+ end_config.setdefault("tcp", []).append({"server_ip": c2_address, "usage": "c2"})
installdir = unicode_string_from_offset(bytes(tmp_config), 0x2A8, 128)
if installdir:
- end_config["directory"] = installdir
+ end_config.setdefault("paths", []).append({"path": installdir, "usage": "install"})
executable = unicode_string_from_offset(tmp_config, 0x4B0, 128)
if executable:
- end_config["filename"] = executable
+ end_config.setdefault("paths", []).append({"path": executable, "usage": "install"})
servicename = unicode_string_from_offset(tmp_config, 0x530, 128)
+ service = {}
if servicename:
- end_config["servicename"] = servicename
+ service["name"] = servicename
displayname = unicode_string_from_offset(tmp_config, 0x738, 128)
if displayname:
- end_config["servicedisplayname"] = displayname
+ service["display_name"] = displayname
description = unicode_string_from_offset(tmp_config, 0x940, 512)
if description:
- end_config["servicedescription"] = description
+ service["description"] = description
+
+ if service:
+ end_config["services"] = [service]
return end_config
diff --git a/modules/processing/parsers/CAPE/REvil.py b/modules/processing/parsers/CAPE/REvil.py
index c5c20c48439..340a7e47151 100644
--- a/modules/processing/parsers/CAPE/REvil.py
+++ b/modules/processing/parsers/CAPE/REvil.py
@@ -19,6 +19,9 @@
import pefile
+AUTHOR = "R3MRUM"
+DESCRIPTION = "REvil configuration parser."
+
def getSectionNames(sections):
return [section.Name.partition(b"\0")[0] for section in sections]
@@ -44,10 +47,14 @@ def decodeREvilConfig(config_key, config_data):
# print(f"Key:\t{key}")
+ if not encoded_config:
+ # No config to decode
+ return
+
ECX = EAX = ESI = 0
for char in init255:
- ESI = ((char & 0xFF) + (ord(key[EAX % len(key)]) + ESI)) & 0xFF
+ ESI = ((char & 0xFF) + (key[EAX % len(key)] + ESI)) & 0xFF
init255[EAX] = init255[ESI] & 0xFF
EAX += 1
init255[ESI] = char & 0xFF
@@ -61,7 +68,7 @@ def decodeREvilConfig(config_key, config_data):
ESI = (ESI + DL) & 0xFF
init255[ECX] = init255[ESI]
init255[ESI] = DL
- decoded_config.append((init255[((init255[ECX] + DL) & 0xFF)]) ^ ord(char))
+ decoded_config.append((init255[((init255[ECX] + DL) & 0xFF)]) ^ char)
EAX = LOCAL1
return json.loads("".join(map(chr, decoded_config)))
@@ -70,16 +77,23 @@ def decodeREvilConfig(config_key, config_data):
def extract_config(data):
config_data = ""
config_key = ""
- pe = pefile.PE(data=data)
-
- if len(pe.sections) == 5:
- section_names = getSectionNames(pe.sections)
- required_sections = (".text", ".rdata", ".data", ".reloc")
-
- # print section_names
- if all(sections in section_names for sections in required_sections):
- # print("all required section names found")
- config_section_name = [resource for resource in section_names if resource not in required_sections][0]
- config_key, config_data = getREvilKeyAndConfig(pe.sections, config_section_name)
- if config_key and config_data:
- return decodeREvilConfig(config_key, config_data)
+ try:
+ pe = pefile.PE(data=data)
+
+ if len(pe.sections) == 5:
+ section_names = getSectionNames(pe.sections)
+ required_sections = (b".text", b".rdata", b".data", b".reloc")
+
+ # print section_names
+ if all(sections in section_names for sections in required_sections):
+ # print("all required section names found")
+ config_section_name = [resource for resource in section_names if resource not in required_sections][0]
+ config_key, config_data = getREvilKeyAndConfig(pe.sections, config_section_name)
+ if config_key and config_data:
+ config = decodeREvilConfig(config_key, config_data)
+ if config:
+ return {"family": "REvil", "other": config}
+ except pefile.PEFormatError:
+ # This isn't a PE file, therefore unlikely to extract a configuration
+ pass
+ return {}
diff --git a/modules/processing/parsers/CAPE/RedLeaf.py b/modules/processing/parsers/CAPE/RedLeaf.py
index 3bffb4cbb72..bbe845a40ce 100644
--- a/modules/processing/parsers/CAPE/RedLeaf.py
+++ b/modules/processing/parsers/CAPE/RedLeaf.py
@@ -12,14 +12,15 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-DESCRIPTION = "RedLeaf configuration parser."
-AUTHOR = "kevoreilly"
-
import struct
import pefile
import yara
+DESCRIPTION = "RedLeaf configuration parser."
+AUTHOR = "kevoreilly"
+
+
rule_source = """
rule RedLeaf
{
@@ -89,21 +90,21 @@ def extract_config(filebuf):
end_config = {}
c2_address = tmp_config[8 : 8 + MAX_IP_STRING_SIZE]
if c2_address:
- end_config.setdefault("c2_address", []).append(c2_address)
+ end_config.setdefault("tcp", []).append({"server_ip": c2_address, "usage": "c2"})
c2_address = tmp_config[0x48 : 0x48 + MAX_IP_STRING_SIZE]
if c2_address:
- end_config.setdefault("c2_address", []).append(c2_address)
+ end_config.setdefault("tcp", []).append({"server_ip": c2_address, "usage": "c2"})
c2_address = tmp_config[0x88 : 0x88 + MAX_IP_STRING_SIZE]
if c2_address:
- end_config.setdefault("c2_address", []).append(c2_address)
+ end_config.setdefault("tcp", []).append({"server_ip": c2_address, "usage": "c2"})
missionid = string_from_offset(tmp_config, 0x1EC)
if missionid:
- end_config["missionid"] = missionid
+ end_config["campaign_id"] = missionid
mutex = unicode_string_from_offset(tmp_config, 0x508)
if mutex:
- end_config["mutex"] = mutex
+ end_config["mutex"] = [mutex]
key = string_from_offset(tmp_config, 0x832)
if key:
- end_config["key"] = key
+ end_config["encryption"] = [{"algorithm": "RC4", "key": key}]
return end_config
diff --git a/modules/processing/parsers/CAPE/Remcos.py b/modules/processing/parsers/CAPE/Remcos.py
index 34cbc071758..f10fa464cae 100644
--- a/modules/processing/parsers/CAPE/Remcos.py
+++ b/modules/processing/parsers/CAPE/Remcos.py
@@ -6,11 +6,9 @@
# By Talos July 2018 - https://github.com/Cisco-Talos/remcos-decoder
# Updates based on work presented here https://gist.github.com/sysopfb/11e6fb8c1377f13ebab09ab717026c87
-DESCRIPTION = "Remcos config extractor."
-AUTHOR = "threathive,sysopfb,kevoreilly"
-
import base64
import logging
+import os
import re
import string
from collections import OrderedDict
@@ -18,8 +16,12 @@
import pefile
from Cryptodome.Cipher import ARC4
+DESCRIPTION = "Remcos config extractor."
+AUTHOR = "threathive,sysopfb,kevoreilly"
+
+
# From JPCERT
-FLAG = {b"\x00": "Disable", b"\x01": "Enable"}
+FLAG = {b"\x00": "Disabled", b"\x01": "Enabled"}
# From JPCERT
idx_list = {
@@ -151,7 +153,6 @@ def check_version(filedata):
def extract_config(filebuf):
config = {}
-
try:
pe = pefile.PE(data=filebuf)
blob = False
@@ -162,32 +163,61 @@ def extract_config(filebuf):
break
if blob:
+ config = {"family": "Remcos", "category": ["rat"]}
keylen = blob[0]
key = blob[1 : keylen + 1]
decrypted_data = ARC4.new(key).decrypt(blob[keylen + 1 :])
p_data = OrderedDict()
- p_data["Version"] = check_version(filebuf)
+ version = check_version(filebuf)
+ if version:
+ config["version"] = version
configs = re.split(rb"\|\x1e\x1e\x1f\|", decrypted_data)
for i, cont in enumerate(configs):
if cont in (b"\x00", b"\x01"):
- p_data[idx_list[i]] = FLAG[cont]
+ # Flag capabilities that are enabled/disabled whether known or not
+ config.setdefault(f"capability_{FLAG[cont].lower()}", []).append(idx_list[i])
elif i in (9, 16, 25, 37):
p_data[idx_list[i]] = setup_list[int(cont)]
elif i in (56, 57, 58):
- p_data[idx_list[i]] = base64.b64encode(cont)
+ config.setdefault("other", {})[idx_list[i]] = base64.b64encode(cont)
elif i == 0:
- host, port, password = cont.split(b"|", 1)[0].split(b":")
- p_data["Control"] = f"tcp://{host.decode()}:{port.decode()}:{password.decode()}"
+ host, port, password = cont.split(b"|", 1)[0].decode().split(":")
+ config.setdefault("tcp", []).append({"server_ip": host, "server_port": port, "usage": "other"})
+ config.setdefault("password", []).append(password)
else:
- p_data[idx_list[i]] = cont
+ p_data[idx_list[i]] = cont.decode()
+
+ # Flag paths
+ for path_key in [k for k in p_data.keys() if k.endswith("path")]:
+ prefix = path_key.split(" ")[0]
+ usage = "other"
+ if prefix == "Install":
+ usage = "install"
+ elif prefix == "Keylog":
+ usage = "logs"
+
+ def get_string(key) -> str:
+ value = p_data.pop(key, None)
+ if key in utf_16_string_list:
+ if isinstance(value, str):
+ value = value.encode()
+ value = value.decode("utf16").strip("\00")
+ return value
+
+ path_parts = [get_string(f"{prefix} {path_part}") for path_part in ["path", "folder", "file"]]
+ full_path = os.path.join(*[p for p in path_parts if p])
+ config.setdefault("paths", []).append({"path": full_path, "usage": usage})
for k, v in p_data.items():
if k in utf_16_string_list:
v = v.decode("utf16").strip("\00")
- config[k] = v
+ config.setdefault("other", {})[k] = v
+ except pefile.PEFormatError:
+ # Not a PE file
+ pass
except Exception as e:
logger.error(f"Caught an exception: {e}")
diff --git a/modules/processing/parsers/CAPE/Retefe.py b/modules/processing/parsers/CAPE/Retefe.py
index 2f22b039501..318a8e25db9 100644
--- a/modules/processing/parsers/CAPE/Retefe.py
+++ b/modules/processing/parsers/CAPE/Retefe.py
@@ -3,14 +3,15 @@
# http://tomasuh.github.io/2018/12/28/retefe-unpack.html
# Many thanks to Tomasuh
-DESCRIPTION = "Retefe configuration parser."
-AUTHOR = "Tomasuh"
-
import struct
import pefile
import yara
+DESCRIPTION = "Retefe configuration parser."
+AUTHOR = "Tomasuh"
+
+
rule_source = """
rule Retefe
{
@@ -34,7 +35,7 @@ def yara_scan(raw_data):
yara_rules = yara.compile(source=rule_source)
matches = yara_rules.match(data=raw_data)
for match in matches:
- if match.rule == "Emotet":
+ if match.rule == "Retefe":
for item in match.strings:
addresses[item[1]] = item[0]
return addresses
@@ -117,10 +118,10 @@ def extract_config(filebuf):
n = 0
result = ""
for ch in buffer:
- result += chr((ord(ch) ^ xor_arr[n % 4]))
+ result += chr(ch ^ xor_arr[n % 4])
n += 1
- return {"Script": result}
+ return {"family": "Retefe", "decoded_strings": [result]}
# Some logical reasoning left behind....
diff --git a/modules/processing/parsers/CAPE/SmallNet.py b/modules/processing/parsers/CAPE/SmallNet.py
index 58aaee142bd..9343264f8b1 100644
--- a/modules/processing/parsers/CAPE/SmallNet.py
+++ b/modules/processing/parsers/CAPE/SmallNet.py
@@ -1,14 +1,23 @@
+import os
+from gettext import install
+
+
def ver_52(data):
config_parts = data.split("!!<3SAFIA<3!!")
+ config = {
+ "family": "SmallNet",
+ "category": ["rat"],
+ "version": "5.2",
+ "tcp": [
+ {"server_domain": config_parts[1], "server_port": config_parts[2]},
+ {"server_domain": config_parts[5], "usage": "c2"}, # Install Server
+ ],
+ "registry": [{"key": config_parts[8]}],
+ }
config_dict = {
- "Domain": config_parts[1],
- "Port": config_parts[2],
- "Disbale Registry": config_parts[3],
- "Disbale TaskManager": config_parts[4],
- "Install Server": config_parts[5],
- "Registry Key": config_parts[8],
- "Install Name": config_parts[9],
- "Disbale UAC": config_parts[10],
+ "Disable Registry": config_parts[3],
+ "Disable TaskManager": config_parts[4],
+ "Disable UAC": config_parts[10],
"Anti-Sandboxie": config_parts[13],
"Anti-Anubis": config_parts[14],
"Anti-VirtualBox": config_parts[15],
@@ -21,7 +30,7 @@ def ver_52(data):
"MSN Spread": config_parts[22],
"Yahoo Spread": config_parts[23],
"LAN Spread": config_parts[24],
- "Disbale Firewall": config_parts[25],
+ "Disable Firewall": config_parts[25],
"Delay Execution MiliSeconds": config_parts[26],
"Attribute Read Only": config_parts[27],
"Attribute System File": config_parts[28],
@@ -46,28 +55,37 @@ def ver_52(data):
"MessageBox Title": config_parts[47],
}
+ install_path = config_parts[9]
if config_parts[6] == 1:
- config_dict["Install Path"] = "Temp"
+ install_path = os.path.join("Temp", config_parts[9])
if config_parts[7] == 1:
- config_dict["Install Path"] = "Windows"
+ install_path = os.path.join("Windows", config_parts[9])
if config_parts[11] == 1:
- config_dict["Install Path"] = "System32"
+ install_path = os.path.join("System32", config_parts[9])
if config_parts[12] == 1:
- config_dict["Install Path"] = "Program Files"
- return config_dict
+ install_path = os.path.join("Program Files", config_parts[9])
+
+ config["paths"] = [{"path": install_path, "usage": "install"}]
+ config["other"] = config_dict # Placing in other for now
+ return config
def ver_5(data):
config_parts = data.split("!!ElMattadorDz!!")
+ config = {
+ "family": "SmallNet",
+ "category": ["rat"],
+ "version": "5",
+ "tcp": [
+ {"server_domain": config_parts[1], "server_port": config_parts[2]},
+ {"server_domain": config_parts[5], "usage": "c2"}, # Install Server
+ ],
+ "registry": [{"key": config_parts[8]}],
+ }
config_dict = {
- "Domain": config_parts[1],
- "Port": config_parts[2],
"Disable Registry": config_parts[3],
- "Disbale TaskManager": config_parts[4],
- "Install Server": config_parts[5],
- "Registry Key": config_parts[8],
- "Install Name": config_parts[9],
- "Disbale UAC": config_parts[10],
+ "Disable TaskManager": config_parts[4],
+ "Disable UAC": config_parts[10],
"Anti-Sandboxie": config_parts[13],
"Anti-Anubis": config_parts[14],
"Anti-VirtualBox": config_parts[15],
@@ -80,22 +98,32 @@ def ver_5(data):
"MSN Spread": config_parts[22],
"Yahoo Spread": config_parts[23],
"LAN Spread": config_parts[24],
- "Disbale Firewall": config_parts[25],
+ "Disable Firewall": config_parts[25],
"Delay Execution MiliSeconds": config_parts[26],
}
+ install_path = config_parts[9]
if config_parts[6] == 1:
- config_dict["Install Path"] = "Temp"
+ install_path = os.path.join("Temp", config_parts[9])
if config_parts[7] == 1:
- config_dict["Install Path"] = "Windows"
+ install_path = os.path.join("Windows", config_parts[9])
if config_parts[11] == 1:
- config_dict["Install Path"] = "System32"
+ install_path = os.path.join("System32", config_parts[9])
if config_parts[12] == 1:
- config_dict["Install Path"] = "Program Files"
- return [config_dict, [config_dict["Domain"]]]
+ install_path = os.path.join("Program Files", config_parts[9])
+ config["paths"] = [{"path": install_path, "usage": "install"}]
+ config["other"] = config_dict # Placing in other for now
+
+ return config
def extract_config(data):
+ try:
+ if isinstance(data, bytes):
+ data = data.decode()
+ except:
+ return
+
if "!!<3SAFIA<3!!" in data:
return ver_52(data)
diff --git a/modules/processing/parsers/CAPE/SquirrelWaffle.py b/modules/processing/parsers/CAPE/SquirrelWaffle.py
index 3e6d46c5619..65dd431e54a 100644
--- a/modules/processing/parsers/CAPE/SquirrelWaffle.py
+++ b/modules/processing/parsers/CAPE/SquirrelWaffle.py
@@ -17,6 +17,9 @@
import pefile
import yara
+AUTHOR = "kevoreilly"
+DESCRIPTION = "SquirrelWaffle configuration parser."
+
rule_source = """
rule SquirrelWaffle
{
@@ -69,12 +72,13 @@ def extract_config(data):
if "\r\n" in decrypted and "|" not in decrypted:
config["IP Blocklist"] = list(filter(None, decrypted.split("\r\n")))
elif "|" in decrypted and "." in decrypted and "\r\n" not in decrypted:
- config["URLs"] = list(filter(None, decrypted.split("|")))
+ config.setdefault("http", []).extend([{"uri": uri} for uri in list(filter(None, decrypted.split("|")))])
except Exception:
continue
matches = yara_rules.match(data=data)
if not matches:
return config
+ config["family"] = "SquirrelWaffle"
for match in matches:
if match.rule != "SquirrelWaffle":
continue
@@ -83,5 +87,5 @@ def extract_config(data):
c2key_offset = int(item[0])
key_rva = struct.unpack("i", data[c2key_offset + 28 : c2key_offset + 32])[0] - pe.OPTIONAL_HEADER.ImageBase
key_offset = pe.get_offset_from_rva(key_rva)
- config["C2 key"] = string_from_offset(data, key_offset).decode()
+ config.setdefault("other", {})["C2 key"] = string_from_offset(data, key_offset).decode()
return config
diff --git a/modules/processing/parsers/CAPE/Strrat.py b/modules/processing/parsers/CAPE/Strrat.py
index c6d5390185c..d446dc7cd7c 100644
--- a/modules/processing/parsers/CAPE/Strrat.py
+++ b/modules/processing/parsers/CAPE/Strrat.py
@@ -75,6 +75,27 @@ def extract_config(data):
configdata = unzip_config(tmpzip)
if configdata:
- raw_config["config"] = decode(configdata)
+ c2_1, mutex_1, dl_url, c2_2, mutex_2, startup_persist, secondary_persist, skype_persist, license = decode(configdata).split(
+ "|"
+ )
+ raw_config["tcp"] = [{"server_domain": c2_1, "usage": "c2"}, {"server_domain": c2_2, "usage": "c2"}]
+ raw_config["http"] = [{"uri": dl_url, "usage": "download"}]
+ raw_config["mutex"] = [mutex_1, mutex_2]
+ raw_config["other"]["license"] = license
+
+ if startup_persist == "true":
+ raw_config.setdefault("capability_enabled", []).append("Setup Startup Folder Persistence")
+ else:
+ raw_config.setdefault("capability_disabled", []).append("Setup Startup Folder Persistence")
+
+ if secondary_persist == "true":
+ raw_config.setdefault("capability_enabled", []).append("Secondary Startup Folder Persistence")
+ else:
+ raw_config.setdefault("capability_disabled", []).append("Secondary Startup Folder Persistence")
+
+ if skype_persist == "true":
+ raw_config.setdefault("capability_enabled", []).append("Setup Skype Scheduled Task Persistence")
+ else:
+ raw_config.setdefault("capability_disabled", []).append("Setup Skype Scheduled Task Persistence")
return raw_config
diff --git a/modules/processing/parsers/CAPE/TSCookie.py b/modules/processing/parsers/CAPE/TSCookie.py
index 4575ea30d9d..1565a819e61 100644
--- a/modules/processing/parsers/CAPE/TSCookie.py
+++ b/modules/processing/parsers/CAPE/TSCookie.py
@@ -30,8 +30,8 @@
# Config pattern
CONFIG_PATTERNS = [
- re.compile("\xC3\x90\x68(....)\xE8(....)\x59\x6A\x01\x58\xC3", re.DOTALL),
- re.compile("\x6A\x04\x68(....)\x8D(.....)\x56\x50\xE8", re.DOTALL),
+ re.compile("\xC3\x90\x68(....)\xE8(....)\x59\x6A\x01\x58\xC3".encode(), re.DOTALL),
+ re.compile("\x6A\x04\x68(....)\x8D(.....)\x56\x50\xE8".encode(), re.DOTALL),
]
CONFIG_SIZE = 0x8D4
@@ -65,17 +65,22 @@ def parse_config(config):
config_dict = collections.OrderedDict()
for i in range(4):
if config[0x10 + 0x100 * i] != "\x00":
- config_dict[f"Server name #{i + 1}"] = __format_string(
- unpack_from("<240s", config, 0x10 + 0x100 * i)[0].decode("utf-16")
+ server_name = (__format_string(unpack_from("<240s", config, 0x10 + 0x100 * i)[0].decode("utf-16")),)
+ main_port = unpack_from("I', config, 0x604)[0]:X}"
- config_dict["Sleep time"] = unpack_from("I', config, 0x604)[0]:X}"}]
+ config_dict["sleep_delay"] = [unpack_from(".
-DESCRIPTION = "Zloader configuration parser"
-AUTHOR = "kevoreilly"
-
import logging
import struct
@@ -22,6 +19,10 @@
import yara
from Cryptodome.Cipher import ARC4
+DESCRIPTION = "Zloader configuration parser"
+AUTHOR = "kevoreilly"
+
+
log = logging.getLogger(__name__)
rule_source = """
@@ -65,18 +66,19 @@ def extract_config(filebuf):
for item in match.strings:
if "$decrypt_conf" in item[1]:
decrypt_conf = int(item[0]) + 21
+ end_config["family"] = "Zloader"
va = struct.unpack("I", filebuf[decrypt_conf : decrypt_conf + 4])[0]
key = string_from_offset(filebuf, pe.get_offset_from_rva(va - image_base))
data_offset = pe.get_offset_from_rva(struct.unpack("I", filebuf[decrypt_conf + 5 : decrypt_conf + 9])[0] - image_base)
enc_data = filebuf[data_offset:].split(b"\0\0", 1)[0]
raw = decrypt_rc4(key, enc_data)
items = list(filter(None, raw.split(b"\x00\x00")))
- end_config["Botnet name"] = items[1].lstrip(b"\x00")
- end_config["Campaign ID"] = items[2]
+ end_config.setdefault("other", {})["Botnet name"] = items[1].lstrip(b"\x00")
+ end_config["campaign_id"] = items[2]
for item in items:
item = item.lstrip(b"\x00")
if item.startswith(b"http"):
- end_config.setdefault("address", []).append(item)
+ end_config.setdefault("http", []).append({"uri": item})
elif len(item) == 16:
- end_config["RC4 key"] = item
+ end_config["encryption"] = [{"algorithm": "RC4", "key": item}]
return end_config
diff --git a/modules/processing/parsers/CAPE/deprecated/SmokeLoader.py b/modules/processing/parsers/CAPE/deprecated/SmokeLoader.py
index a43c1d9bdcb..b346e9dc87b 100644
--- a/modules/processing/parsers/CAPE/deprecated/SmokeLoader.py
+++ b/modules/processing/parsers/CAPE/deprecated/SmokeLoader.py
@@ -12,14 +12,14 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-DESCRIPTION = "SmokeLoader configuration parser."
-AUTHOR = "kevoreilly"
-
import struct
import pefile
import yara
+AUTHOR = "kevoreilly"
+DESCRIPTION = "SmokeLoader configuration parser."
+
rule_source = """
rule SmokeLoader
{
@@ -66,10 +66,10 @@ def extract_config(filebuf):
except Exception:
image_base = 0
- end_config = {}
- matches = yara_scan(filebuf, "$")
- if matches["$ref64_1"]:
- table_ref_offset = int(matches["$ref64_1"])
+ end_config = {"family": "SmokeLoader"}
+ table_ref = yara_scan(filebuf, "$ref64_1")
+ if table_ref:
+ table_ref_offset = int(table_ref["$ref64_1"])
table_delta = struct.unpack("i", filebuf[table_ref_offset + 62 : table_ref_offset + 66])[0]
table_offset = table_ref_offset + table_delta + 66
@@ -94,15 +94,17 @@ def extract_config(filebuf):
c2_key = struct.unpack("I", filebuf[c2_offset + c2_size + 1 : c2_offset + c2_size + 5])[0]
c2_url = xor_decode(filebuf[c2_offset + 1 : c2_offset + c2_size + 1], c2_key).decode("ascii")
if c2_url:
- end_config.setdefault("address", []).append(c2_url)
+ end_config.setdefault("http", []).append({"uri": c2_url, "usage": "c2"})
except Exception:
table_loop = False
else:
table_loop = False
table_offset += 8
return end_config
- elif matches["$ref64_2"]:
- table_ref_offset = int(matches["$ref64_1"])
+ else:
+ table_ref = yara_scan(filebuf, "$ref64_2")
+ if table_ref:
+ table_ref_offset = int(table_ref["$ref64_2"])
table_delta = struct.unpack("i", filebuf[table_ref_offset + 26 : table_ref_offset + 30])[0]
table_offset = table_ref_offset + table_delta + 30
@@ -117,13 +119,15 @@ def extract_config(filebuf):
try:
c2_url = xor_decode(filebuf[c2_offset + 1 : c2_offset + c2_size + 1], c2_key).decode("ascii")
if c2_url:
- end_config.setdefault("address", []).append(c2_url)
+ end_config.setdefault("http", []).append({"uri": c2_url, "usage": "c2"})
except Exception:
pass
table_offset += 8
return end_config
- elif matches["$ref32_1"]:
- table_ref_offset = int(matches["$ref32_1"])
+ else:
+ table_ref = yara_scan(filebuf, "$ref32_1")
+ if table_ref:
+ table_ref_offset = int(table_ref["$ref32_1"])
table_rva = struct.unpack("i", filebuf[table_ref_offset + 55 : table_ref_offset + 59])[0] - image_base
table_offset = pe.get_offset_from_rva(table_rva)
@@ -148,7 +152,7 @@ def extract_config(filebuf):
c2_key = struct.unpack("I", filebuf[c2_offset + c2_size + 1 : c2_offset + c2_size + 5])[0]
c2_url = xor_decode(filebuf[c2_offset + 1 : c2_offset + c2_size + 1], c2_key).decode("ascii")
if c2_url:
- end_config.setdefault("address", []).append(c2_url)
+ end_config.setdefault("http", []).append({"uri": c2_url, "usage": "c2"})
except Exception:
table_loop = False
else:
diff --git a/modules/processing/parsers/CAPE/test_cape.py b/modules/processing/parsers/CAPE/test_cape.py
index f9190c3c306..b30b0476a2b 100644
--- a/modules/processing/parsers/CAPE/test_cape.py
+++ b/modules/processing/parsers/CAPE/test_cape.py
@@ -1,2 +1,2 @@
-def extract_config():
+def extract_config(data: bytes):
pass
diff --git a/modules/processing/parsers/CAPE/unrecom.py b/modules/processing/parsers/CAPE/unrecom.py
index 172369bc4ce..51a1f08d534 100644
--- a/modules/processing/parsers/CAPE/unrecom.py
+++ b/modules/processing/parsers/CAPE/unrecom.py
@@ -1,21 +1,25 @@
import string
import xml.etree.ElementTree as ET
-from io import StringIO
-from zipfile import ZipFile
+from io import BytesIO, StringIO
+from zipfile import BadZipFile, ZipFile
from Cryptodome.Cipher import ARC4
def extract_embedded(zip_data):
raw_embedded = None
- archive = StringIO(zip_data)
- with ZipFile(archive) as zip:
- for name in zip.namelist(): # get all the file names
- if name == "load/ID": # contains first part of key
- partial_key = zip.read(name)
- enckey = f"{partial_key}DESW7OWKEJRU4P2K" # complete key
- if name == "load/MANIFEST.MF": # this is the embedded jar
- raw_embedded = zip.read(name)
+ archive = BytesIO(zip_data) if isinstance(zip_data, bytes) else StringIO(zip_data)
+ try:
+ with ZipFile(archive) as zip:
+ for name in zip.namelist(): # get all the file names
+ if name == "load/ID": # contains first part of key
+ partial_key = zip.read(name)
+ enckey = f"{partial_key}DESW7OWKEJRU4P2K" # complete key
+ if name == "load/MANIFEST.MF": # this is the embedded jar
+ raw_embedded = zip.read(name)
+ except BadZipFile:
+ # File is not a zip
+ pass
if raw_embedded is None:
return None
# Decrypt the raw file
@@ -47,16 +51,22 @@ def parse_config(config):
else:
raw_config[child.attrib["key"]] = child.text
return {
- "Version": raw_config["Version"],
- "Delay": raw_config["delay"],
- "Domain": raw_config["dns"],
- "Extension": raw_config["extensionname"],
- "Install": raw_config["install"],
- "Port1": raw_config["p1"],
- "Port2": raw_config["p2"],
- "Password": raw_config["password"],
- "PluginFolder": raw_config["pluginfoldername"],
- "Prefix": raw_config["prefix"],
+ "family": "unrecom",
+ "version": raw_config["Version"],
+ "sleep_delay": [raw_config["delay"]],
+ "password": [raw_config["password"]],
+ "paths": [
+ {"path": raw_config["pluginfoldername"], "usage": "plugins"},
+ {"path": raw_config["install"], "usage": "install"},
+ ],
+ "other": {
+ # Need context around how these are used TCP/HTTP connections
+ "Prefix": raw_config["prefix"],
+ "Domain": raw_config["dns"],
+ "Extension": raw_config["extensionname"],
+ "Port1": raw_config["p1"],
+ "Port2": raw_config["p2"],
+ },
}