Merge remote-tracking branch 'origin/develop' into feature/refactor-monkey-island

This commit is contained in:
Itay Mizeretz 2017-09-26 11:22:28 +03:00
commit 243a255833
7 changed files with 203 additions and 18 deletions

View File

@ -1,13 +1,14 @@
import os import os
import sys import sys
from network.range import FixedRange, RelativeRange, ClassCRange import types
from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter, RdpExploiter, SSHExploiter, ShellShockExploiter,\ import uuid
SambaCryExploiter
from network import TcpScanner, PingScanner, SMBFinger, SSHFinger, HTTPFinger
from abc import ABCMeta from abc import ABCMeta
from itertools import product from itertools import product
import uuid
import types from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter, RdpExploiter, SSHExploiter, ShellShockExploiter, \
SambaCryExploiter
from network import TcpScanner, PingScanner, SMBFinger, SSHFinger, HTTPFinger, MySQLFinger, ElasticFinger
from network.range import FixedRange
__author__ = 'itamar' __author__ = 'itamar'
@ -15,6 +16,7 @@ GUID = str(uuid.getnode())
EXTERNAL_CONFIG_FILE = os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), 'monkey.bin') EXTERNAL_CONFIG_FILE = os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), 'monkey.bin')
def _cast_by_example(value, example): def _cast_by_example(value, example):
""" """
a method that casts a value to the type of the parameter given as example a method that casts a value to the type of the parameter given as example
@ -140,9 +142,9 @@ class Configuration(object):
max_iterations = 1 max_iterations = 1
scanner_class = TcpScanner scanner_class = TcpScanner
finger_classes = [SMBFinger, SSHFinger, PingScanner, HTTPFinger] finger_classes = [SMBFinger, SSHFinger, PingScanner, HTTPFinger, MySQLFinger, ElasticFinger]
exploiter_classes = [SmbExploiter, WmiExploiter, RdpExploiter, Ms08_067_Exploiter, # Windows exploits exploiter_classes = [SmbExploiter, WmiExploiter, RdpExploiter, Ms08_067_Exploiter, # Windows exploits
SSHExploiter, ShellShockExploiter, SambaCryExploiter # Linux SSHExploiter, ShellShockExploiter, SambaCryExploiter, # Linux
] ]
# how many victims to look for in a single scan iteration # how many victims to look for in a single scan iteration
@ -178,7 +180,7 @@ class Configuration(object):
range_class = FixedRange range_class = FixedRange
range_size = 1 range_size = 1
range_fixed = ['',] range_fixed = ['', ]
blocked_ips = ['', ] blocked_ips = ['', ]
@ -186,7 +188,17 @@ class Configuration(object):
HTTP_PORTS = [80, 8080, 443, HTTP_PORTS = [80, 8080, 443,
8008, # HTTP alternate 8008, # HTTP alternate
] ]
tcp_target_ports = [22, 2222, 445, 135, 3389] tcp_target_ports = [22,
2222,
445,
135,
3389,
80,
8080,
443,
8008,
3306,
9200]
tcp_target_ports.extend(HTTP_PORTS) tcp_target_ports.extend(HTTP_PORTS)
tcp_scan_timeout = 3000 # 3000 Milliseconds tcp_scan_timeout = 3000 # 3000 Milliseconds
tcp_scan_interval = 200 tcp_scan_interval = 200
@ -211,13 +223,17 @@ class Configuration(object):
# User and password dictionaries for exploits. # User and password dictionaries for exploits.
def get_exploit_user_password_pairs(self): def get_exploit_user_password_pairs(self):
"""
Returns all combinations of the configurations users and passwords
:return:
"""
return product(self.exploit_user_list, self.exploit_password_list) return product(self.exploit_user_list, self.exploit_password_list)
exploit_user_list = ['Administrator', 'root', 'user'] exploit_user_list = ['Administrator', 'root', 'user']
exploit_password_list = ["Password1!", "1234", "password", "12345678"] exploit_password_list = ["Password1!", "1234", "password", "12345678"]
# smb/wmi exploiter # smb/wmi exploiter
smb_download_timeout = 300 # timeout in seconds smb_download_timeout = 300 # timeout in seconds
smb_service_name = "InfectionMonkey" smb_service_name = "InfectionMonkey"
# Timeout (in seconds) for sambacry's trigger to yield results. # Timeout (in seconds) for sambacry's trigger to yield results.
@ -243,7 +259,6 @@ class Configuration(object):
# Monkey copy filename on share (64 bit) # Monkey copy filename on share (64 bit)
sambacry_monkey_copy_filename_64 = "monkey64_2" sambacry_monkey_copy_filename_64 = "monkey64_2"
# system info collection # system info collection
collect_system_info = True collect_system_info = True
@ -253,4 +268,5 @@ class Configuration(object):
mimikatz_dll_name = "mk.dll" mimikatz_dll_name = "mk.dll"
WormConfiguration = Configuration() WormConfiguration = Configuration()

View File

@ -39,7 +39,9 @@
"SSHFinger", "SSHFinger",
"PingScanner", "PingScanner",
"HTTPFinger", "HTTPFinger",
"SMBFinger" "SMBFinger",
"MySQLFinger"
"ElasticFinger",
], ],
"max_iterations": 3, "max_iterations": 3,
"monkey_log_path_windows": "%temp%\\~df1563.tmp", "monkey_log_path_windows": "%temp%\\~df1563.tmp",
@ -83,7 +85,9 @@
80, 80,
8080, 8080,
443, 443,
8008 3306,
8008,
9200
], ],
"timeout_between_iterations": 10, "timeout_between_iterations": 10,
"use_file_logging": true, "use_file_logging": true,

View File

@ -23,5 +23,7 @@ from tcp_scanner import TcpScanner
from smbfinger import SMBFinger from smbfinger import SMBFinger
from sshfinger import SSHFinger from sshfinger import SSHFinger
from httpfinger import HTTPFinger from httpfinger import HTTPFinger
from elasticfinger import ElasticFinger
from mysqlfinger import MySQLFinger
from info import local_ips from info import local_ips
from info import get_free_tcp_port from info import get_free_tcp_port

View File

@ -0,0 +1,48 @@
import json
import logging
from contextlib import closing
import requests
from requests.exceptions import Timeout, ConnectionError
from model.host import VictimHost
from network import HostFinger
ES_PORT = 9200
ES_SERVICE = 'elastic-search-9200'
ES_HTTP_TIMEOUT = 5
LOG = logging.getLogger(__name__)
__author__ = 'danielg'
class ElasticFinger(HostFinger):
"""
Fingerprints elastic search clusters, only on port 9200
"""
def __init__(self):
self._config = __import__('config').WormConfiguration
def get_host_fingerprint(self, host):
"""
Returns elasticsearch metadata
:param host:
:return: Success/failure, data is saved in the host struct
"""
assert isinstance(host, VictimHost)
try:
url = 'http://%s:%s/' % (host.ip_addr, ES_PORT)
with closing(requests.get(url, timeout=ES_HTTP_TIMEOUT)) as req:
data = json.loads(req.text)
host.services[ES_SERVICE] = {}
host.services[ES_SERVICE]['cluster_name'] = data['cluster_name']
host.services[ES_SERVICE]['name'] = data['name']
host.services[ES_SERVICE]['version'] = data['version']['number']
return True
except Timeout:
LOG.debug("Got timeout while trying to read header information")
except ConnectionError: # Someone doesn't like us
LOG.debug("Unknown connection error")
except KeyError:
LOG.debug("Failed parsing the ElasticSearch JSOn response")
return False

View File

@ -0,0 +1,85 @@
import logging
import socket
from model.host import VictimHost
from network import HostFinger
from .tools import struct_unpack_tracker, struct_unpack_tracker_string
MYSQL_PORT = 3306
SQL_SERVICE = 'mysqld-3306'
LOG = logging.getLogger(__name__)
class MySQLFinger(HostFinger):
"""
Fingerprints mysql databases, only on port 3306
"""
SOCKET_TIMEOUT = 0.5
HEADER_SIZE = 4 # in bytes
def __init__(self):
self._config = __import__('config').WormConfiguration
def get_host_fingerprint(self, host):
"""
Returns mySQLd data using the host header
:param host:
:return: Success/failure, data is saved in the host struct
"""
assert isinstance(host, VictimHost)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(self.SOCKET_TIMEOUT)
try:
s.connect((host.ip_addr, MYSQL_PORT))
header = s.recv(self.HEADER_SIZE) # max header size?
response, curpos = struct_unpack_tracker(header, 0, "I")
response = response[0]
response_length = response & 0xff # first byte is significant
data = s.recv(response_length)
# now we can start parsing
protocol, curpos = struct_unpack_tracker(data, 0, "B")
protocol = protocol[0]
if protocol == 0xFF:
# error code, bug out
LOG.debug("Mysql server returned error")
return False
version, curpos = struct_unpack_tracker_string(data, curpos) # special coded to solve string parsing
version = version[0]
host.services[SQL_SERVICE] = {}
host.services[SQL_SERVICE]['version'] = version
version = version.split('-')[0].split('.')
host.services[SQL_SERVICE]['major_version'] = version[0]
host.services[SQL_SERVICE]['minor_version'] = version[1]
host.services[SQL_SERVICE]['build_version'] = version[2]
thread_id, curpos = struct_unpack_tracker(data, curpos, "<I") # ignore thread id
# protocol parsing taken from
# https://nmap.org/nsedoc/scripts/mysql-info.html
if protocol == 10:
# new protocol
self._parse_protocol_10(curpos, data, host)
return True
if protocol == 9:
return True
s.close()
except Exception as err:
LOG.debug("Error getting mysql fingerprint: %s", err)
return False
def _parse_protocol_10(self, curpos, data, host):
salt, curpos = struct_unpack_tracker(data, curpos, "s8B")
capabilities, curpos = struct_unpack_tracker(data, curpos, "<H")
host.services[SQL_SERVICE]['capabilities'] = capabilities[0]
charset, curpos = struct_unpack_tracker(data, curpos, "B")
status, curpos = struct_unpack_tracker(data, curpos, "<H")
extcapabilities, curpos = struct_unpack_tracker(data, curpos, "<H")
host.services[SQL_SERVICE]['extcapabilities'] = extcapabilities[0]
# there's more data but it doesn't matter

View File

@ -1,6 +1,7 @@
import socket import socket
import select import select
import logging import logging
import struct
DEFAULT_TIMEOUT = 10 DEFAULT_TIMEOUT = 10
BANNER_READ = 1024 BANNER_READ = 1024
@ -8,6 +9,32 @@ BANNER_READ = 1024
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
def struct_unpack_tracker(data, index, fmt):
"""
Unpacks a struct from the specified index according to specified format.
Returns the data and the next index
:param data: Buffer
:param index: Position index
:param fmt: Struct format
:return: (Data, new index)
"""
unpacked = struct.unpack_from(fmt, data, index)
return unpacked, struct.calcsize(fmt)
def struct_unpack_tracker_string(data, index):
"""
Unpacks a null terminated string from the specified index
Returns the data and the next index
:param data: Buffer
:param index: Position index
:return: (Data, new index)
"""
ascii_len = data[index:].find('\0')
fmt = "%ds" % ascii_len
return struct_unpack_tracker(data,index,fmt)
def check_port_tcp(ip, port, timeout=DEFAULT_TIMEOUT, get_banner=False): def check_port_tcp(ip, port, timeout=DEFAULT_TIMEOUT, get_banner=False):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout) sock.settimeout(timeout)

View File

@ -1,8 +1,10 @@
import sys
import socket import socket
import sys
import psutil import psutil
from enum import IntEnum from enum import IntEnum
from network.info import get_host_subnets, local_ips
from network.info import get_host_subnets
__author__ = 'uri' __author__ = 'uri'
@ -68,7 +70,7 @@ class InfoCollector(object):
"cmdline": " ".join(process.cmdline()), "cmdline": " ".join(process.cmdline()),
"full_image_path": process.exe(), "full_image_path": process.exe(),
} }
except psutil.AccessDenied: except (psutil.AccessDenied, WindowsError):
# we may be running as non root # we may be running as non root
# and some processes are impossible to acquire in Windows/Linux # and some processes are impossible to acquire in Windows/Linux
# in this case we'll just add what we can # in this case we'll just add what we can
@ -78,7 +80,8 @@ class InfoCollector(object):
"cmdline": "ACCESS DENIED", "cmdline": "ACCESS DENIED",
"full_image_path": "null", "full_image_path": "null",
} }
pass continue
self.info['process_list'] = processes self.info['process_list'] = processes
def get_network_info(self): def get_network_info(self):