Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Have CAPE adopt MACO format #1037

Merged
merged 23 commits into from
Aug 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
3b9432a
Fix DoppelPaymer extractor
cccs-rs Jun 27, 2022
95ee4fa
MACO conversion init
cccs-rs Aug 2, 2022
4291f8b
MACO conversion init
cccs-rs Aug 2, 2022
653cace
Merge branch 'master' of github.com:cccs-rs/CAPEv2
cccs-rs Aug 2, 2022
cbd63ee
update IcedIDLoader.py according to origin
cccs-rs Aug 2, 2022
a560423
style: Automatic code formatting
actions-user Aug 2, 2022
e1ca4c4
MACO conversion init
cccs-rs Aug 2, 2022
95e0c43
MACO conversion init
cccs-rs Aug 2, 2022
12792d0
update IcedIDLoader.py according to origin
cccs-rs Aug 2, 2022
c62dd43
style: Automatic code formatting
actions-user Aug 2, 2022
de40e2f
Merge branch 'master' of github.com:cccs-rs/CAPEv2
cccs-rs Aug 25, 2022
9445781
fix variable changes
cccs-rs Aug 25, 2022
4a75dce
style: Automatic code formatting
actions-user Aug 25, 2022
a7f1e49
Fix parsers for Assemblyline service
cccs-rs Aug 25, 2022
6c5c5e4
Merge branch 'master' of github.com:cccs-rs/CAPEv2
cccs-rs Aug 25, 2022
335840a
style: Automatic code formatting
actions-user Aug 25, 2022
173f15a
Escape reserved Python characters in CobaltStrikeBeacon rule
cccs-rs Aug 25, 2022
b31ece5
Merge branch 'master' of github.com:cccs-rs/CAPEv2
cccs-rs Aug 25, 2022
83ee818
Convert CobaltStrikeBeacon output to MACO
cccs-rs Aug 25, 2022
fa39933
style: Automatic code formatting
actions-user Aug 25, 2022
49965ea
empty versions not allowed
cccs-rs Aug 26, 2022
ecd2292
style: Automatic code formatting
actions-user Aug 26, 2022
fe603ab
Merge branch 'master' into pr/1037
doomedraven Aug 28, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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