Skip to content

Commit

Permalink
Have CAPE adopt MACO format (#1037)
Browse files Browse the repository at this point in the history
  • Loading branch information
cccs-rs authored Aug 28, 2022
1 parent 44387ea commit bbee903
Show file tree
Hide file tree
Showing 42 changed files with 934 additions and 496 deletions.
55 changes: 46 additions & 9 deletions modules/processing/parsers/CAPE/AsyncRat.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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 {}
Expand Down
7 changes: 4 additions & 3 deletions modules/processing/parsers/CAPE/Azorult.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"])

Expand All @@ -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
49 changes: 31 additions & 18 deletions modules/processing/parsers/CAPE/BackOffLoader.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from Cryptodome.Cipher import ARC4

CFG_START = "1020304050607080"
AUTHOR = "CAPE"
DESCRIPTION = "BackOffLoader configuration parser."


def RC4(key, data):
Expand All @@ -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


Expand Down
49 changes: 30 additions & 19 deletions modules/processing/parsers/CAPE/BackOffPOS.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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


Expand Down
11 changes: 6 additions & 5 deletions modules/processing/parsers/CAPE/BitPaymer.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,16 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

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
{
Expand Down Expand Up @@ -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
78 changes: 41 additions & 37 deletions modules/processing/parsers/CAPE/BlackNix.py
Original file line number Diff line number Diff line change
@@ -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):
Expand All @@ -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 {}
Loading

0 comments on commit bbee903

Please sign in to comment.