Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/kevoreilly/cape
Browse files Browse the repository at this point in the history
  • Loading branch information
kevoreilly committed Feb 6, 2020
2 parents d18daaf + 13e9063 commit e8a6b51
Show file tree
Hide file tree
Showing 15 changed files with 414 additions and 17 deletions.
Binary file removed analyzer/windows/dll/Hancitor.dll
Binary file not shown.
Binary file modified analyzer/windows/dll/capemon.dll
Binary file not shown.
Binary file modified analyzer/windows/dll/capemon_x64.dll
Binary file not shown.
43 changes: 43 additions & 0 deletions analyzer/windows/modules/packages/Combo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Copyright (C) 2010-2015 Cuckoo Foundation, Optiv, Inc. ([email protected])
# This file is part of Cuckoo Sandbox - http://www.cuckoosandbox.org
# See the file 'docs/LICENSE' for copying permission.

import os
import shutil
from subprocess import call
from lib.common.abstracts import Package

class Combo(Package):
"""CAPE Combo analysis package."""

def __init__(self, options={}, config=None):
"""@param options: options dict."""
self.config = config
self.options = options
self.pids = []
self.options["combo"] = "1"

def start(self, path):
args = self.options.get("arguments")
appdata = self.options.get("appdata")
runasx86 = self.options.get("runasx86")

# If the file doesn't have an extension, add .exe
# See CWinApp::SetCurrentHandles(), it will throw
# an exception that will crash the app if it does
# not find an extension on the main exe's filename
if "." not in os.path.basename(path):
new_path = path + ".exe"
os.rename(path, new_path)
path = new_path

if appdata:
# run the executable from the APPDATA directory, required for some malware
basepath = os.getenv('APPDATA')
newpath = os.path.join(basepath, os.path.basename(path))
shutil.copy(path, newpath)
path = newpath
if runasx86:
# ignore the return value, user must have CorFlags.exe installed in the guest VM
call(["CorFlags.exe", path, "/32bit+"])
return self.execute(path, args, path)
47 changes: 47 additions & 0 deletions analyzer/windows/modules/packages/Combo_dll.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Copyright (C) 2010-2015 Cuckoo Foundation.
# This file is part of Cuckoo Sandbox - http://www.cuckoosandbox.org
# See the file 'docs/LICENSE' for copying permission.

import os
import shutil

from lib.common.abstracts import Package

class Combo_dll(Package):
"""CAPE Combo DLL analysis package."""
PATHS = [
("SystemRoot", "system32", "rundll32.exe"),
]

def __init__(self, options={}, config=None):
"""@param options: options dict."""
self.config = config
self.options = options
self.options["combo"] = "1"

def start(self, path):
rundll32 = self.get_path("rundll32.exe")
function = self.options.get("function", "#1")
arguments = self.options.get("arguments")
dllloader = self.options.get("dllloader")

# Check file extension.
ext = os.path.splitext(path)[-1].lower()
# If the file doesn't have the proper .dll extension force it
# and rename it. This is needed for rundll32 to execute correctly.
# See ticket #354 for details.
if ext != ".dll":
new_path = path + ".dll"
os.rename(path, new_path)
path = new_path

args = "{0},{1}".format(path, function)
if arguments:
args += " {0}".format(arguments)

if dllloader:
newname = os.path.join(os.path.dirname(rundll32), dllloader)
shutil.copy(rundll32, newname)
rundll32 = newname

return self.execute(rundll32, args, path)
3 changes: 2 additions & 1 deletion analyzer/windows/modules/packages/Emotet.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ def __init__(self, options={}, config=None):
self.pids = []
self.options["extraction"] = "1"
self.options["procdump"] = "0"
self.options["exclude-apis"] = "RegOpenKeyExA"
self.options["single-process"] = "1"
self.options["exclude-apis"] = "RegOpenKeyExA:SendMessageA"

def start(self, path):
args = self.options.get("arguments")
Expand Down
3 changes: 1 addition & 2 deletions analyzer/windows/modules/packages/Hancitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ def __init__(self, options={}, config=None):
self.config = config
self.options = options
self.pids = []
self.options["dll"] = "Hancitor.dll"
#self.options["dll_64"] = "Hancitor_x64.dll"
self.options["hancitor"] = "1"

def start(self, path):
args = self.options.get("arguments")
Expand Down
42 changes: 42 additions & 0 deletions analyzer/windows/modules/packages/Hancitor_dll.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Copyright (C) 2010-2015 Cuckoo Foundation.
# This file is part of Cuckoo Sandbox - http://www.cuckoosandbox.org
# See the file 'docs/LICENSE' for copying permission.

import os
import shutil

from lib.common.abstracts import Package

class Hancitor_Dll(Package):
"""CAPE Hancitor DLL analysis package."""
PATHS = [
("SystemRoot", "system32", "rundll32.exe"),
]

def start(self, path):
rundll32 = self.get_path("rundll32.exe")
function = self.options.get("function", "#1")
arguments = self.options.get("arguments")
dllloader = self.options.get("dllloader")
self.options["hancitor"] = "1"

# Check file extension.
ext = os.path.splitext(path)[-1].lower()
# If the file doesn't have the proper .dll extension force it
# and rename it. This is needed for rundll32 to execute correctly.
# See ticket #354 for details.
if ext != ".dll":
new_path = path + ".dll"
os.rename(path, new_path)
path = new_path

args = "\"{0}\",{1}".format(path, function)
if arguments:
args += " {0}".format(arguments)

if dllloader:
newname = os.path.join(os.path.dirname(rundll32), dllloader)
shutil.copy(rundll32, newname)
rundll32 = newname

return self.execute(rundll32, args, path)
3 changes: 1 addition & 2 deletions analyzer/windows/modules/packages/Hancitor_doc.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ def __init__(self, options={}, config=None):
"""@param options: options dict."""
self.config = config
self.options = options
self.options["dll"] = "Hancitor.dll"
#self.options["dll_64"] = "Hancitor_x64.dll"
self.options["hancitor"] = "1"

def start(self, path):
word = self.get_path_glob("Microsoft Office Word")
Expand Down
5 changes: 5 additions & 0 deletions conf/processing.conf
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ dnswhitelist = no
# additional entries
dnswhitelist_file = extra/whitelist_domains.txt

# Should the server use a compressed version of behavioural logs? This helps
# in saving space in Mongo, accelerates searchs and reduce the size of the
# final JSON report.
[loop_detection]
enabled = no

[static]
enabled = yes
Expand Down
17 changes: 10 additions & 7 deletions data/yara/CAPE/REvil.yar
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ rule REvil
description = "REvil Payload"
cape_type = "REvil Payload"
strings:
$RE1 = "expand 32-byte kexpand 16-byte k" ascii fullword
$RE2 = "sysshadow" ascii fullword
$RE3 = "SCROLLBAR" ascii fullword
$RE4 = "msctfime ui" ascii fullword
$RE5 = "\\BaseNamedObjects\\%S" wide fullword
$decode = {33 D2 8A 9C 3D FC FE FF FF 8B C7 0F B6 CB F7 75 0C 8B 45 08 0F B6 04 02 03 C6 03 C8 0F B6 F1 8A 84 35 FC FE FF FF 88 84 3D FC FE FF FF 47 88 9C 35 FC FE FF FF 81 FF 00 01 00 00 72 C3}
$OtherRE1 = "expand 32-byte kexpand 16-byte k" ascii fullword
$OtherRE2 = "sysshadow" ascii fullword
$OtherRE3 = "SCROLLBAR" ascii fullword
$OtherRE4 = "msctfime ui" ascii fullword
$OtherRE5 = "\\BaseNamedObjects\\%S" wide fullword
$RE_dec = "rwdec_x86_debug.pdb" ascii
$GCREvil_string_decoder_opcodes = {33 D2 8A 9C 3D FC FE FF FF 8B C7 0F B6 CB F7 75 0C 8B 45 08 0F B6 04 02 03 C6 03 C8 0F B6 F1 8A 84 35 FC FE FF FF 88 84 3D FC FE FF FF 47 88 9C 35 FC FE FF FF 81 FF 00 01 00 00 72 C3 }
$REvil_string_decoder_opcodes1 = {8B C1 8A 1C 39 33 D2 0F B6 CB F7 75 ?? 8B 45 ?? 0F B6 04 02 03 C6 03 C8 0F B6 F1 8B 4D ?? 8A 04 3E 88 04 39 41 88 1C 3E 89 4D ?? 81 F9 00 01 00 00 }
condition:
uint16(0) == 0x5A4D and $decode and any of ($RE*)
uint16(0) == 0x5a4d
and (($GCREvil_string_decoder_opcodes and any of ($OtherRE*)) or any of ($REvil_string_decoder_opcodes*)) and not $RE_dec
}
189 changes: 189 additions & 0 deletions lib/cuckoo/common/compressor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import os
import struct
import binascii
import logging

log = logging.getLogger(__name__)

try:
import bson
HAVE_BSON = True
except ImportError:
HAVE_BSON = False
else:
# The BSON module provided by pymongo works through its "BSON" class.
if hasattr(bson, "BSON"):
bson_decode = lambda d: bson.BSON(d).decode()
# The BSON module provided by "pip install bson" works through the
# "loads" function (just like pickle etc.)
elif hasattr(bson, "loads"):
bson_decode = lambda d: bson.loads(d)
else:
HAVE_BSON = False

class NGram:
def __init__(self, order):
self.order = order
self.buffer = []

def add(self, element):
tmp = None
if not element:
return tmp

if len(self.buffer) == self.order * 2:
tmp = self.buffer.pop(0)

if type(element) == list:
self.buffer.append(element)
else:
self.buffer.append([element, 1])

self.analyse()
return tmp

def analyse(self):
tmp = [c[0][0] for c in self.buffer]
if tmp[0:self.order] == tmp[self.order:]:
for i in range(self.order):
self.buffer[i][1] += self.buffer[i+self.order][1]
self.buffer = self.buffer[0:self.order]

class Compressor:
def __init__(self, level):
self.level = level
self.ngrams = [ NGram(i) for i in range(1,level+1) ]
self.final = []

def add(self, element):
head, tail = (self.ngrams[0], self.ngrams[1:])
out = head.add(element)

for t in tail:
out = t.add(out)

if out:
self.final.append(out)

def flush(self):
for i in range(len(self.ngrams)):
current_buffer = self.ngrams[i].buffer
for out in current_buffer:
for u in range(i+1, len(self.ngrams)):
out = self.ngrams[u].add(out)
if out:
self.final.append(out)

class CuckooBsonCompressor:
def __init__(self):
self.threads = {}
self.callmap = {}
self.head = []
self.ccounter = 0

def __next_message(self):
data = self.fd_in.read(4)
if not data:
return (False, False)
_size = struct.unpack('I', data)[0]
data += self.fd_in.read(_size - 4)
self.raw_data = data
return (data, bson_decode(data))

def run(self, file_path):
if not os.path.isfile(file_path) and os.stat(file_path).st_size:
log.warning('File %s does not exists or it is invalid.', file_path)
return False

self.fd_in = open(file_path, 'rb')

msg = '---'
while msg:
data, msg = self.__next_message()

if msg:
mtype = msg.get('type') # message type [debug, new_process, info]
if mtype not in ['debug', 'new_process', 'info']:
_id = msg.get('I', -1)
if not self.category.startswith('__'):
tid = msg.get('T', -1)
time = msg.get('t', 0)

if tid not in self.threads:
self.threads[tid] = Compressor(100)

csum = self.checksum(msg)
self.ccounter += 1
v = (csum, self.ccounter, time)
self.threads[tid].add(v)

if csum not in self.callmap:
self.callmap[csum] = msg
else:
self.head.append(data)
else:
self.category = msg.get('category', 'None')
self.head.append(data)

self.fd_in.close()

return self.flush(file_path)

def flush(self, file_path):
# This function flushes ngram buffers within compressor and merges
# threads compressed call lists trying preserve original order

compressed_path = file_path + '.compressed'
if os.path.isfile(compressed_path):
os.remove(compressed_path)

fd = open(compressed_path, 'wb')

for d in self.head:
fd.write(d)

final = []
for tid, c in self.threads.items():
c.flush()
for element, repeated in c.final:
data = self.callmap.get(element[0]).copy()
data['r'] += repeated
data['t'] = element[2]
data['order'] = element[1]
final.append(data)

final.sort(key=lambda x: x['order'])

if final and os.path.isfile(compressed_path):
for d in final:
d.pop('order')
edata = bson.BSON.encode(d)
fd.write(edata)

os.rename(file_path, '{}.raw'.format(file_path))
os.symlink('{}.compressed'.format(file_path), file_path)
else:
return False

return True

def checksum(self, msg):
# This function calculates a 4 bytes checksum for each call
# this value is used for identifying a call setup.

index = msg.get('I', -1)
args = ''.join([ str(c) for c in msg['args'] ])
content = [
str(index), # api call
str(msg['T']), # thread id
str(msg['R']), # caller
str(args), # call args
str(self.category), # category
str(msg['P']) # parentcaller
]
content = ''.join(content)

# Python3 version
# return binascii.crc32(bytes(content, 'utf8'))
return binascii.crc32(content)

Loading

0 comments on commit e8a6b51

Please sign in to comment.