Skip to content

Commit

Permalink
Added support for WanaCry, Cerber and Emotet.
Browse files Browse the repository at this point in the history
  • Loading branch information
kevoreilly committed May 16, 2017
1 parent 0b94fa5 commit baf9146
Show file tree
Hide file tree
Showing 14 changed files with 300 additions and 16 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Currently CAPE has specific packages dumping configuration and payloads for the
- EvilGrab
- Azzy

Many other malware families have their payloads extracted by some of the behavioural packages, with their configuration in the clear in the resulting output. Configuration parsing may then be performed on this by virtue of yara-based detection, and config parsing based on CAPE's primary config parsing framework, DC3-MWCP (Defense Cyber Crime Center - Malware Configuration Parser). Parsers may also be written using the RATDecoders parser from malwareconfig.com. Thanks to DC3 and Kevin Breen/TechAnarchy for these frameworks.
Many other malware families have their payloads extracted by some of the behavioural packages, with their configuration in the clear in the resulting output. Configuration parsing may then be performed on this by virtue of Yara-based detection, and config parsing based on CAPE's primary config parsing framework, DC3-MWCP (Defense Cyber Crime Center - Malware Configuration Parser). Parsers may also be written using the RATDecoders framework from malwareconfig.com. Thanks to DC3 and Kevin Breen/TechAnarchy for these frameworks.

CAPE has config parsers for the following malware families, whose payloads are extracted by a behavioural package:
- HttpBrowser
Expand All @@ -41,7 +41,7 @@ There are a number of other behavioural and malware family packages and parsers

Packages can be written based on API hooks, the CAPE debugger, or a combination of both.

The CAPE debugger allows four breakpoints to be set on each malware thread to detect on read, write or execute of a memory region, as well as single-step mode. This allows fine control over malware execution until it is possible to dump the memory regions of interest, containing code or configuration data. Breakpoints can be set dynamically by package code or via yara signatures.
The CAPE debugger allows four breakpoints to be set on each malware thread to detect on read, write or execute of a memory region, as well as single-step mode. This allows fine control over malware execution until it is possible to dump the memory regions of interest, containing code or configuration data. Breakpoints can be set dynamically by package code or via Yara signatures.

Processes, modules and memory regions can variously be dumped by CAPE through use of a simple API. These dumps can then be scanned and parsed for configuration information.

Expand Down
Binary file modified analyzer/windows/dll/Extraction.dll
Binary file not shown.
10 changes: 8 additions & 2 deletions cuckoo.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from lib.cuckoo.common.exceptions import CuckooCriticalError
from lib.cuckoo.common.exceptions import CuckooDependencyError
from lib.cuckoo.core.database import Database
from lib.cuckoo.core.startup import check_working_directory, check_configs, check_signatures, cuckoo_clean, cuckoo_clean_failed_tasks, cuckoo_clean_failed_url_tasks,cuckoo_clean_before_day,cuckoo_clean_sorted_pcap_dump,cuckoo_clean_bson_suri_logs
from lib.cuckoo.core.startup import check_working_directory, check_configs, check_signatures, cuckoo_clean, cuckoo_clean_failed_tasks, cuckoo_clean_failed_url_tasks,cuckoo_clean_before_day,cuckoo_clean_sorted_pcap_dump,cuckoo_clean_bson_suri_logs, cuckoo_clean_pending_tasks
from lib.cuckoo.core.startup import create_structure
from lib.cuckoo.core.startup import init_logging, init_modules, init_console_logging
from lib.cuckoo.core.startup import init_tasks, init_yara
Expand Down Expand Up @@ -90,7 +90,7 @@ def cuckoo_main(max_analysis_count=0):
parser.add_argument("-t", "--test", help="Test startup", action="store_true", required=False)
parser.add_argument("-m", "--max-analysis-count", help="Maximum number of analyses", type=int, required=False)
parser.add_argument("--clean", help="Remove all tasks and samples and their associated data", action='store_true', required=False)
parser.add_argument("--failed-clean", help="Remove all tasks maked as failed", action='store_true', required=False)
parser.add_argument("--failed-clean", help="Remove all tasks marked as failed", action='store_true', required=False)
parser.add_argument("--failed-url-clean", help="Remove all tasks that are url tasks but we don't have any HTTP traffic", action='store_true', required=False)
parser.add_argument("--delete-older-than-days", help="Remove all tasks older than X number of days", type=int, required=False)
parser.add_argument("--pcap-sorted-clean", help="remove sorted pcap from jobs", action="store_true", required=False)
Expand All @@ -99,6 +99,7 @@ def cuckoo_main(max_analysis_count=0):
parser.add_argument("--files-only-filter",help="only remove files events filter DELETE AFTER ONLY", action="store_true", required=False)
parser.add_argument("--custom-include-filter",help="Only include jobs that match the custom field DELETE AFTER ONLY", required=False)
parser.add_argument("--bson-suri-logs-clean",help="clean bson and suri logs from analysis dirs",required=False, action="store_true")
parser.add_argument("--pending-clean",help="Remove all tasks marked as failed",required=False, action="store_true")
args = parser.parse_args()

if args.clean:
Expand All @@ -124,6 +125,11 @@ def cuckoo_main(max_analysis_count=0):
if args.bson_suri_logs_clean:
cuckoo_clean_bson_suri_logs()
sys.exit(0)

if args.pending_clean:
cuckoo_clean_pending_tasks()
sys.exit(0)

try:
cuckoo_init(quiet=args.quiet, debug=args.debug, artwork=args.artwork,
test=args.test)
Expand Down
17 changes: 17 additions & 0 deletions data/yara/CAPE/Cerber.yar
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
rule Cerber
{
meta:
author = "kevoreilly"
description = "Cerber Payload"
cape_type = "Cerber Payload"
strings:
$code1 = {33 C0 66 89 45 88 8D 7D 8A AB AB AB AB AB 66 AB 8D 45 88 E8 54 60 00 00 C7 04 24 90 37}
condition:
//check for MZ Signature at offset 0
uint16(0) == 0x5A4D
and
all of them
}

20 changes: 20 additions & 0 deletions data/yara/CAPE/Emotet.yar
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
rule Emotet
{
meta:
author = "kevoreilly"
description = "Emotet Payload"
cape_type = "Emotet Payload"
strings:
$string1 = "%s\\es.l.k"
$string2 = "%s\\es\\%3.ex%"
$string2 = "AlllAsclAttlBac"
$decrypt = {69 C9 3F 00 01 00 8D 52 01 0F BE C0 03 C8 8A 02 84 C0 75 EC 33 4D 0C 33 F6 85 FF}
condition:
//check for MZ Signature at offset 0
uint16(0) == 0x5A4D
and
all of them
}

21 changes: 21 additions & 0 deletions data/yara/CAPE/WanaCry.yar
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
rule WanaCry
{
meta:
author = "kevoreilly"
description = "WanaCry Payload"
cape_type = "WanaCry Payload"
strings:
$exename = "@[email protected]"
$res = "%08X.res"
$pky = "%08X.pky"
$eky = "%08X.eky"
$taskstart = {8B 35 58 71 00 10 53 68 C0 D8 00 10 68 F0 DC 00 10 FF D6 83 C4 0C 53 68 B4 D8 00 10 68 24 DD 00 10 FF D6 83 C4 0C 53 68 A8 D8 00 10 68 58 DD 00 10 FF D6 53}
condition:
//check for MZ Signature at offset 0
uint16(0) == 0x5A4D
and
all of them
}

14 changes: 14 additions & 0 deletions data/yara/binaries/HeavensGate.yar
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
rule HeavensGate
{
meta:
author = "kevoreilly"
description = "Heaven's Gate: Switch from 32-bit to 64-mode"
cape_type = "Heaven's Gate"

strings:
$gate_v1 = {6A 33 E8 00 00 00 00 83 04 24 05 CB}
$gate_v2 = {9A 00 00 00 00 33 00 89 EC 5D C3 48 83 EC 20 E8 00 00 00 00 48 83 C4 20 CB}
condition:
($gate_v1 or $gate_v2)
}
8 changes: 6 additions & 2 deletions lib/cuckoo/common/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,8 +329,12 @@ def get_yara(self, rulepath=YARA_RULEPATH):
if self.guest_paths:
filepath = self.guest_paths[0]
rules = yara.compile(rulepath, externals={"filepath":filepath, "filename":filename})
except:
rules = yara.compile(rulepath)
except yara.SyntaxError as e:
if 'duplicated identifier' in e.args[0]:
log.warning("Duplicate rule in %s, rulepath")
log.warning(e.args[0])
else:
rules = yara.compile(rulepath)
matches = rules.match(self.file_path)

# This is ancient, and breaks CAPE, so removing.
Expand Down
45 changes: 43 additions & 2 deletions lib/cuckoo/core/startup.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from lib.cuckoo.common.exceptions import CuckooStartupError
from lib.cuckoo.common.exceptions import CuckooOperationalError
from lib.cuckoo.common.utils import create_folders, store_temp_file, delete_folder
from lib.cuckoo.core.database import Database, Task, TASK_RUNNING, TASK_FAILED_ANALYSIS, TASK_FAILED_PROCESSING, TASK_FAILED_REPORTING, TASK_RECOVERED, TASK_REPORTED
from lib.cuckoo.core.database import Database, Task, TASK_RUNNING, TASK_PENDING, TASK_FAILED_ANALYSIS, TASK_FAILED_PROCESSING, TASK_FAILED_REPORTING, TASK_RECOVERED, TASK_REPORTED
from lib.cuckoo.core.plugins import import_plugin, import_package, list_plugins

log = logging.getLogger()
Expand Down Expand Up @@ -400,7 +400,7 @@ def cuckoo_clean_failed_tasks():
delete_folder(os.path.join(CUCKOO_ROOT, "storage", "analyses",
"%s" % int(new["id"])))
else:
print "failed to remove faile task %s from DB" % (int(new["id"]))
print "failed to remove failed task %s from DB" % (int(new["id"]))

def cuckoo_clean_bson_suri_logs():
"""Clean up raw suri log files probably not needed if storing in mongo. Does not remove extracted files
Expand Down Expand Up @@ -604,3 +604,44 @@ def cuckoo_clean_sorted_pcap_dump():
done = True
else:
done = True

def cuckoo_clean_pending_tasks():
"""Clean up pending tasks
It deletes all stored data from file system and configured databases (SQL
and MongoDB for pending tasks.
"""
# Init logging.
# This need to init a console logger handler, because the standard
# logger (init_logging()) logs to a file which will be deleted.
create_structure()
init_console_logging()

# Initialize the database connection.
db = Database()

# Check if MongoDB reporting is enabled and drop that if it is.
cfg = Config("reporting")
if cfg.mongodb and cfg.mongodb.enabled:
from pymongo import MongoClient
host = cfg.mongodb.get("host", "127.0.0.1")
port = cfg.mongodb.get("port", 27017)
mdb = cfg.mongodb.get("db", "cuckoo")
try:
results_db = MongoClient(host, port)[mdb]
except:
log.warning("Unable to connect to MongoDB database: %s", mdb)
return

pending_tasks = db.list_tasks(status=TASK_PENDING)
for e in pending_tasks:
new = e.to_dict()
print int(new["id"])
try:
results_db.analysis.remove({"info.id": int(new["id"])})
except:
print "failed to remove analysis info (may not exist) %s" % (int(new["id"]))
if db.delete_task(new["id"]):
delete_folder(os.path.join(CUCKOO_ROOT, "storage", "analyses",
"%s" % int(new["id"])))
else:
print "failed to remove pending task %s from DB" % (int(new["id"]))
3 changes: 3 additions & 0 deletions modules/reporting/submitCAPE.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ def run(self, results):
db = Database()
detections = set()

if self.task_options and 'disable_cape=1' in self.task_options:
return

parent_package = report["info"].get("package")
if parent_package in cape_package_list:
# we only want to trigger detections from 'straight' runs, behavioural packages or unpackers
Expand Down
16 changes: 9 additions & 7 deletions modules/signatures/CAPE.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@
IMAGE_FILE_EXECUTABLE_IMAGE = 0x0002
PE_HEADER_LIMIT = 0x200

PLUGX_SIGNATURE = 0x5658
EXECUTABLE_FLAGS = 0x10 | 0x20 | 0x40 | 0x80
EXTRACTION_MIN_SIZE = 0x1001

PLUGX_SIGNATURE = 0x5658

class CAPE_Compression(Signature):
name = "Compression"
description = "CAPE detection: Compression (or decompression)"
Expand Down Expand Up @@ -98,22 +100,22 @@ def __init__(self, *args, **kwargs):

def on_call(self, call, process):
if call["api"] == "NtAllocateVirtualMemory":
protection = self.get_argument(call, "Protection")
protection = int(self.get_raw_argument(call, "Protection"), 0)
regionsize = int(self.get_raw_argument(call, "RegionSize"), 0)
handle = self.get_argument(call, "ProcessHandle")
if handle == "0xffffffff" and protection == "0x00000040" and regionsize >= EXTRACTION_MIN_SIZE:
if handle == "0xffffffff" and protection & EXECUTABLE_FLAGS and regionsize >= EXTRACTION_MIN_SIZE:
return True
if call["api"] == "VirtualProtectEx":
protection = self.get_argument(call, "Protection")
protection = int(self.get_raw_argument(call, "Protection"), 0)
size = int(self.get_raw_argument(call, "Size"), 0)
handle = self.get_argument(call, "ProcessHandle")
if handle == "0xffffffff" and protection == "0x00000040" and size >= EXTRACTION_MIN_SIZE:
if handle == "0xffffffff" and protection & EXECUTABLE_FLAGS and size >= EXTRACTION_MIN_SIZE:
return True
elif call["api"] == "NtProtectVirtualMemory":
protection = self.get_argument(call, "NewAccessProtection")
protection = int(self.get_raw_argument(call, "NewAccessProtection"), 0)
size = int(self.get_raw_argument(call, "NumberOfBytesProtected"), 0)
handle = self.get_argument(call, "ProcessHandle")
if handle == "0xffffffff" and protection == "0x00000040" and size >= EXTRACTION_MIN_SIZE:
if handle == "0xffffffff" and protection & EXECUTABLE_FLAGS and size >= EXTRACTION_MIN_SIZE:
return True

class CAPE_InjectionCreateRemoteThread(Signature):
Expand Down
Loading

0 comments on commit baf9146

Please sign in to comment.