class STAP(Auxiliary): """system-wide syscall trace with stap.""" priority = -10 # low prio to wrap tightly around the analysis def __init__(self): self.config = Config(cfg="analysis.conf") self.proc = None def start(self): # helper function locating the stap module def has_stap(p): only_stap = [fn for fn in os.listdir(p) if fn.startswith("stap_") and fn.endswith(".ko")] if only_stap: return os.path.join(p, only_stap[0]) return False path_cfg = self.config.get("analyzer_stap_path", None) if path_cfg and os.path.exists(path_cfg): path = path_cfg elif os.path.exists("/root/.cuckoo") and has_stap("/root/.cuckoo"): path = has_stap("/root/.cuckoo") else: log.warning("Could not find STAP LKM, aborting systemtap analysis.") return False stap_start = time.time() self.proc = subprocess.Popen([ "staprun", "-vv", "-x", str(os.getpid()), "-o", "stap.log", path, ], stderr=subprocess.PIPE) while "systemtap_module_init() returned 0" not in self.proc.stderr.readline(): pass stap_stop = time.time() log.info("STAP aux module startup took %.2f seconds" % (stap_stop - stap_start)) return True @staticmethod def _upload_file(local, remote): if os.path.exists(local): nf = NetlogFile(remote) with open(local, "rb") as f: for chunk in f: nf.sock.sendall(chunk) # dirty direct send, no reconnecting nf.close() def stop(self): try: r = self.proc.poll() log.debug("stap subprocess retval %r", r) self.proc.kill() except Exception as e: log.warning("Exception killing stap: %s", e) self._upload_file("stap.log", "logs/all.stap")
def _stap_command_line(target, **kwargs): config = Config(cfg="analysis.conf") def has_stap(p): only_stap = [fn for fn in os.listdir(p) if fn.startswith("stap_") and fn.endswith(".ko")] if only_stap: return os.path.join(p, only_stap[0]) return False path_cfg = config.get("analyzer_stap_path") root_cuckoo_path = os.path.join("/root", ".cuckoo") user_cuckoo_path = os.path.join("/home", "user", ".cuckoo") if path_cfg and os.path.exists(path_cfg): path = path_cfg elif os.path.exists(root_cuckoo_path) and has_stap(root_cuckoo_path): path = has_stap(root_cuckoo_path) elif os.path.exists(user_cuckoo_path) and has_stap(user_cuckoo_path): path = has_stap(user_cuckoo_path) else: log.warning("Could not find STAP LKM, aborting systemtap analysis") return False # cmd = ["sudo", "staprun", "-vv", "-o", "stap.log", path] cmd = f"sudo staprun -vv -o stap.log {path}" target_cmd = f'"{target}"' if "args" in kwargs: target_cmd += f'" {" ".join(kwargs["args"])}"' # When we don't want to run the target as root, we have to drop privileges # with `sudo -u current_user` right before calling the target. # if not kwargs.get("run_as_root", False): # target_cmd = f'"sudo -u {getuser()} {target_cmd}"' # cmd += " -DSUDO=1" # cmd += ["-c", target_cmd] cmd += f" -c {target_cmd}" return cmd
class Analyzer: """Cuckoo Linux Analyzer. This class handles the initialization and execution of the analysis procedure, including the auxiliary modules and the analysis packages. """ def __init__(self): # Parse the analysis configuration file generated by the agent. self.config = Config(cfg="analysis.conf") self.target = None def prepare(self): """Prepare env for analysis.""" # Create the folders used for storing the results. create_folders() # Initialize logging. init_logging() if self.config.get("clock"): # Set virtual machine clock. clock = datetime.datetime.strptime(self.config.clock, "%Y%m%dT%H:%M:%S") # Setting date and time. os.system(f'date -s "{clock.strftime("%y-%m-%d %H:%M:%S")}"') # We update the target according to its category. If it's a file, then # we store the path. if self.config.category == "file": self.target = os.path.join(tempfile.gettempdir(), self.config.file_name) # If it's a URL, well.. we store the URL. elif self.config.category == "archive": zip_path = os.path.join(os.environ.get("TEMP", "/tmp"), self.config.file_name) with zipfile.ZipFile(zip_path) as zf: zf.extractall(os.environ.get("TEMP", "/tmp")) self.target = os.path.join(os.environ.get("TEMP", "/tmp"), self.config.options["filename"]) else: self.target = self.config.target def complete(self): """End analysis.""" # Dump all the notified files. dump_files() # Hell yeah. log.info("Analysis completed") return True def run(self): """Run analysis. @return: operation status. """ self.prepare() log.debug("Starting analyzer from: %s", os.getcwd()) log.debug("Storing results at: %s", PATHS["root"]) # If no analysis package was specified at submission, we try to select # one automatically. """ if not self.config.package: log.debug("No analysis package specified, trying to detect it automagically") package = "generic" if self.config.category == "file" else "wget" # If we weren't able to automatically determine the proper package, # we need to abort the analysis. if not package: raise CuckooError(f"No valid package available for file type: {self.config.file_type}") log.info('Automatically selected analysis package "%s"', package) # Otherwise just select the specified package. else: package = self.config.package # Generate the package path. package_name = f"modules.packages.{package}" # Try to import the analysis package. try: __import__(package_name, globals(), locals(), ["dummy"], 0) # If it fails, we need to abort the analysis. except ImportError: raise CuckooError('Unable to import package "{package_name}", does not exist') # Initialize the package parent abstract. Package() # Enumerate the abstract subclasses. try: package_class = Package.__subclasses__()[0] except IndexError as e: raise CuckooError(f"Unable to select package class (package={package_name}): {e}") """ if self.config.package: suggestion = "ff" if self.config.package == "ie" else self.config.package elif self.config.category != "file": suggestion = "url" else: suggestion = None # Try to figure out what analysis package to use with this target kwargs = {"suggestion": suggestion} if self.config.category == "file": package_class = choose_package_class(self.config.file_type, self.config.file_name, **kwargs) else: package_class = choose_package_class(None, None, **kwargs) if not package_class: raise Exception("Could not find an appropriate analysis package") # Package initialization kwargs = { "options": self.config.options, "timeout": self.config.timeout } # Initialize the analysis package. # pack = package_class(self.config.get_options()) pack = package_class(self.target, **kwargs) # Initialize Auxiliary modules Auxiliary() prefix = f"{auxiliary.__name__}." for loader, name, ispkg in pkgutil.iter_modules( auxiliary.__path__, prefix): if ispkg: continue # Import the auxiliary module. try: __import__(name, globals(), locals(), ["dummy"], 0) except ImportError as e: log.warning('Unable to import the auxiliary module "%s": %s', name, e) # Walk through the available auxiliary modules. aux_enabled, aux_avail = [], [] for module in sorted(Auxiliary.__subclasses__(), key=lambda x: x.priority, reverse=True): # Try to start the auxiliary module. try: aux = module() aux_avail.append(aux) aux.start() except (NotImplementedError, AttributeError): log.warning("Auxiliary module %s was not implemented", aux.__class__.__name__) continue except Exception as e: log.warning("Cannot execute auxiliary module %s: %s", aux.__class__.__name__, e) continue finally: log.debug("Started auxiliary module %s", aux.__class__.__name__) # aux_enabled.append(aux) if aux: aux_enabled.append(aux) # Start analysis package. If for any reason, the execution of the # analysis package fails, we have to abort the analysis. try: # pids = pack.start(self.target) pids = pack.start() except NotImplementedError: raise CuckooError( f'The package "{package_class}" doesn\'t contain a run function' ) except CuckooPackageError as e: raise CuckooError( f'The package "{package_class}" start function raised an error: {e}' ) except Exception as e: raise CuckooError( f'The package "{package_class}" start function encountered an unhandled exception: {e}' ) # If the analysis package returned a list of process IDs, we add them # to the list of monitored processes and enable the process monitor. if pids: add_pids(pids) pid_check = True # If the package didn't return any process ID (for example in the case # where the package isn't enabling any behavioral analysis), we don't # enable the process monitor. else: log.info( "No process IDs returned by the package, running for the full timeout" ) pid_check = False # Check in the options if the user toggled the timeout enforce. If so, # we need to override pid_check and disable process monitor. if self.config.enforce_timeout: log.info("Enabled timeout enforce, running for the full timeout") pid_check = False time_counter = 0 while True: time_counter += 1 if time_counter > int(self.config.timeout): log.info("Analysis timeout hit, terminating analysis") break try: # If the process monitor is enabled we start checking whether # the monitored processes are still alive. if pid_check: for pid in list(PROCESS_LIST): if not Process(pid=pid).is_alive(): log.info("Process with pid %s has terminated", pid) PROCESS_LIST.remove(pid) # ToDo # ask the package if it knows any new pids # add_pids(pack.get_pids()) # also ask the auxiliaries for aux in aux_avail: add_pids(aux.get_pids()) # If none of the monitored processes are still alive, we # can terminate the analysis. if not PROCESS_LIST: log.info("Process list is empty, terminating analysis") break # Update the list of monitored processes available to the # analysis package. It could be used for internal # operations within the module. pack.set_pids(PROCESS_LIST) try: # The analysis packages are provided with a function that # is executed at every loop's iteration. If such function # returns False, it means that it requested the analysis # to be terminate. if not pack.check(): log.info( "The analysis package requested the termination of the analysis" ) break # If the check() function of the package raised some exception # we don't care, we can still proceed with the analysis but we # throw a warning. except Exception as e: log.warning( 'The package "%s" check function raised an exception: %s', package_class, e) except Exception as e: log.exception("The PID watching loop raised an exception: %s", e) finally: # Zzz. time.sleep(1) try: # Before shutting down the analysis, the package can perform some # final operations through the finish() function. pack.finish() except Exception as e: log.warning( 'The package "%s" finish function raised an exception: %s', package_class, e) try: # Upload files the package created to files in the results folder package_files = pack.package_files() if package_files is not None: for package in package_files: upload_to_host(package[0], os.path.join("files", package[1])) except Exception as e: log.warning( 'The package "%s" package_files function raised an exception: %s', package_class, e) # Terminate the Auxiliary modules. for aux in sorted(aux_enabled, key=lambda x: x.priority): try: aux.stop() except (NotImplementedError, AttributeError): continue except Exception as e: log.warning("Cannot terminate auxiliary module %s: %s", aux.__class__.__name__, e) if self.config.terminate_processes: # Try to terminate remaining active processes. We do this to make sure # that we clean up remaining open handles (sockets, files, etc.). log.info("Terminating remaining processes before shutdown") for pid in PROCESS_LIST: proc = Process(pid=pid) if proc.is_alive(): try: proc.terminate() except Exception: continue # Run the finish callback of every available Auxiliary module. for aux in aux_avail: try: aux.finish() except (NotImplementedError, AttributeError): continue except Exception as e: log.warning( "Exception running finish callback of auxiliary module %s: %s", aux.__class__.__name__, e) # Let's invoke the completion procedure. self.complete() return True
class LKM(Auxiliary): """helper LKM for sleep skipping etc""" def __init__(self): self.config = Config(cfg="analysis.conf") self.pids_reported = set() def start(self): # highest priority: if the vm config specifies the path if self.config.get("analyzer_lkm_path", None) and os.path.exists(self.config.get("analyzer_lkm_path")): path = self.config.get("analyzer_lkm_path") # next: if the analyzer was uploaded with a module for our platform elif os.path.exists(os.path.join(platform.machine(), "probelkm.ko")): path = os.path.join(platform.machine(), "probelkm.ko") # next: default path inside the machine elif os.path.exists("/root/.cuckoo/probelkm.ko"): path = "/root/.cuckoo/probelkm.ko" # next: generic module uploaded with the analyzer (single arch setup maybe?) elif os.path.exists("probelkm.ko"): path = "probelkm.ko" else: log.warning("Could not find probelkm :(") return False os.system("insmod %s trace_descendants=1 target_pid=%u" % (path, os.getpid())) return True def get_pids(self): new = [] fd = open("/var/log/kern.log") for line in fd: if not "[probelkm]" in line: continue pos1 = line.find("forked to ") pos2 = line.find("@", pos1+10) if pos1 == -1 or pos2 == -1: continue forked_pid = int(line[pos1+10:pos2]) if forked_pid in self.pids_reported: continue self.pids_reported.add(forked_pid) new.append(forked_pid) return new def stop(self): # i guess we don't need to unload at all #os.system("rmmod probelkm") # now upload the logfile nf = NetlogFile("logs/all.lkm") fd = open("/var/log/kern.log") for line in fd: if not "[probelkm]" in line: continue nf.sock.sendall(line) # dirty direct send, no reconnecting fd.close() nf.close()
class STAP(Auxiliary): """system-wide syscall trace with stap.""" priority = -10 # low prio to wrap tightly around the analysis def __init__(self): self.config = Config(cfg="analysis.conf") self.proc = None def start(self): # helper function locating the stap module def has_stap(p): only_stap = [ fn for fn in os.listdir(p) if fn.startswith("stap_") and fn.endswith(".ko") ] if only_stap: return os.path.join(p, only_stap[0]) return False path_cfg = self.config.get("analyzer_stap_path", None) if path_cfg and os.path.exists(path_cfg): path = path_cfg elif os.path.exists("/root/.cuckoo") and has_stap("/root/.cuckoo"): path = has_stap("/root/.cuckoo") else: log.warning( "Could not find STAP LKM, aborting systemtap analysis.") return False stap_start = time.time() self.proc = subprocess.Popen([ "staprun", "-vv", "-x", str(os.getpid()), "-o", "stap.log", path, ], stderr=subprocess.PIPE) while "systemtap_module_init() returned 0" not in self.proc.stderr.readline( ): pass stap_stop = time.time() log.info("STAP aux module startup took %.2f seconds" % (stap_stop - stap_start)) return True @staticmethod def _upload_file(local, remote): if os.path.exists(local): nf = NetlogFile(remote) with open(local, "rb") as f: for chunk in f: nf.sock.sendall( chunk) # dirty direct send, no reconnecting nf.close() def stop(self): try: r = self.proc.poll() log.debug("stap subprocess retval %r", r) self.proc.kill() except Exception as e: log.warning("Exception killing stap: %s", e) self._upload_file("stap.log", "logs/all.stap")
class Analyzer: """Cuckoo Linux Analyzer. This class handles the initialization and execution of the analysis procedure, including the auxiliary modules and the analysis packages. """ def __init__(self): self.config = None self.target = None def prepare(self): """Prepare env for analysis.""" # Create the folders used for storing the results. create_folders() # Initialize logging. init_logging() # Parse the analysis configuration file generated by the agent. self.config = Config(cfg="analysis.conf") if self.config.get("clock", None): # Set virtual machine clock. clock = datetime.datetime.strptime(self.config.clock, "%Y%m%dT%H:%M:%S") # Setting date and time. os.system("date -s \"{0}\"".format(clock.strftime("%y-%m-%d %H:%M:%S"))) # We update the target according to its category. If it's a file, then # we store the path. if self.config.category == "file": self.target = os.path.join(tempfile.gettempdir(), self.config.file_name) # If it's a URL, well.. we store the URL. else: self.target = self.config.target def complete(self): """End analysis.""" # Dump all the notified files. dump_files() # Hell yeah. log.info("Analysis completed.") def run(self): """Run analysis. @return: operation status. """ self.prepare() log.debug("Starting analyzer from: %s", os.getcwd()) log.debug("Storing results at: %s", PATHS["root"]) # If no analysis package was specified at submission, we try to select # one automatically. if not self.config.package: log.debug("No analysis package specified, trying to detect " "it automagically.") if self.config.category == "file": package = "generic" else: package = "wget" # If we weren't able to automatically determine the proper package, # we need to abort the analysis. if not package: raise CuckooError("No valid package available for file " "type: {0}".format(self.config.file_type)) log.info("Automatically selected analysis package \"%s\"", package) # Otherwise just select the specified package. else: package = self.config.package # Generate the package path. package_name = "modules.packages.%s" % package # Try to import the analysis package. try: __import__(package_name, globals(), locals(), ["dummy"], -1) # If it fails, we need to abort the analysis. except ImportError: raise CuckooError("Unable to import package \"{0}\", does " "not exist.".format(package_name)) # Initialize the package parent abstract. Package() # Enumerate the abstract subclasses. try: package_class = Package.__subclasses__()[0] except IndexError as e: raise CuckooError("Unable to select package class " "(package={0}): {1}".format(package_name, e)) # Initialize the analysis package. pack = package_class(self.config.get_options()) # Initialize Auxiliary modules Auxiliary() prefix = auxiliary.__name__ + "." for loader, name, ispkg in pkgutil.iter_modules(auxiliary.__path__, prefix): if ispkg: continue # Import the auxiliary module. try: __import__(name, globals(), locals(), ["dummy"], -1) except ImportError as e: log.warning("Unable to import the auxiliary module " "\"%s\": %s", name, e) # Walk through the available auxiliary modules. aux_enabled, aux_avail = [], [] for module in sorted(Auxiliary.__subclasses__(), key=lambda x: x.priority, reverse=True): # Try to start the auxiliary module. try: aux = module() aux_avail.append(aux) aux.start() except (NotImplementedError, AttributeError): log.warning("Auxiliary module %s was not implemented", aux.__class__.__name__) continue except Exception as e: log.warning("Cannot execute auxiliary module %s: %s", aux.__class__.__name__, e) continue finally: log.debug("Started auxiliary module %s", aux.__class__.__name__) aux_enabled.append(aux) # Start analysis package. If for any reason, the execution of the # analysis package fails, we have to abort the analysis. try: pids = pack.start(self.target) except NotImplementedError: raise CuckooError("The package \"{0}\" doesn't contain a run " "function.".format(package_name)) except CuckooPackageError as e: raise CuckooError("The package \"{0}\" start function raised an " "error: {1}".format(package_name, e)) except Exception as e: raise CuckooError("The package \"{0}\" start function encountered " "an unhandled exception: " "{1}".format(package_name, e)) # If the analysis package returned a list of process IDs, we add them # to the list of monitored processes and enable the process monitor. if pids: add_pids(pids) pid_check = True # If the package didn't return any process ID (for example in the case # where the package isn't enabling any behavioral analysis), we don't # enable the process monitor. else: log.info("No process IDs returned by the package, running " "for the full timeout.") pid_check = False # Check in the options if the user toggled the timeout enforce. If so, # we need to override pid_check and disable process monitor. if self.config.enforce_timeout: log.info("Enabled timeout enforce, running for the full timeout.") pid_check = False time_counter = 0 while True: time_counter += 1 if time_counter == int(self.config.timeout): log.info("Analysis timeout hit, terminating analysis.") break try: # If the process monitor is enabled we start checking whether # the monitored processes are still alive. if pid_check: for pid in list(PROCESS_LIST): if not Process(pid=pid).is_alive(): log.info("Process with pid %s has terminated", pid) PROCESS_LIST.remove(pid) # ask the package if it knows any new pids add_pids(pack.get_pids()) # also ask the auxiliaries for aux in aux_avail: add_pids(aux.get_pids()) # If none of the monitored processes are still alive, we # can terminate the analysis. if not PROCESS_LIST: log.info("Process list is empty, " "terminating analysis.") break # Update the list of monitored processes available to the # analysis package. It could be used for internal # operations within the module. pack.set_pids(PROCESS_LIST) try: # The analysis packages are provided with a function that # is executed at every loop's iteration. If such function # returns False, it means that it requested the analysis # to be terminate. if not pack.check(): log.info("The analysis package requested the " "termination of the analysis.") break # If the check() function of the package raised some exception # we don't care, we can still proceed with the analysis but we # throw a warning. except Exception as e: log.warning("The package \"%s\" check function raised " "an exception: %s", package_name, e) except Exception as e: log.exception("The PID watching loop raised an exception: %s", e) finally: # Zzz. time.sleep(1) try: # Before shutting down the analysis, the package can perform some # final operations through the finish() function. pack.finish() except Exception as e: log.warning("The package \"%s\" finish function raised an " "exception: %s", package_name, e) try: # Upload files the package created to package_files in the results folder package_files = pack.package_files() if package_files is not None: for package in package_files: upload_to_host( package[0], os.path.join("package_files", package[1]) ) except Exception as e: log.warning("The package \"%s\" package_files function raised an " "exception: %s", package_name, e) # Terminate the Auxiliary modules. for aux in sorted(aux_enabled, key=lambda x: x.priority): try: aux.stop() except (NotImplementedError, AttributeError): continue except Exception as e: log.warning("Cannot terminate auxiliary module %s: %s", aux.__class__.__name__, e) if self.config.terminate_processes: # Try to terminate remaining active processes. We do this to make sure # that we clean up remaining open handles (sockets, files, etc.). log.info("Terminating remaining processes before shutdown.") for pid in PROCESS_LIST: proc = Process(pid=pid) if proc.is_alive(): try: proc.terminate() except: continue # Run the finish callback of every available Auxiliary module. for aux in aux_avail: try: aux.finish() except (NotImplementedError, AttributeError): continue except Exception as e: log.warning("Exception running finish callback of auxiliary " "module %s: %s", aux.__class__.__name__, e) # Let's invoke the completion procedure. self.complete() return True
class STAP(Auxiliary): """System-wide syscall trace with stap.""" priority = -10 # low prio to wrap tightly around the analysis def __init__(self, options={}, analyzer=None): self.config = Config(cfg="analysis.conf") self.proc = None def start(self): # helper function locating the stap module def has_stap(p): only_stap = [ fn for fn in os.listdir(p) if fn.startswith("stap_") and fn.endswith(".ko") ] if only_stap: return os.path.join(p, only_stap[0]) return False path_cfg = self.config.get("analyzer_stap_path", None) if path_cfg and os.path.exists(path_cfg): path = path_cfg elif os.path.exists("/root/.cuckoo") and has_stap("/root/.cuckoo"): path = has_stap("/root/.cuckoo") elif os.path.exists("/root/.cape") and has_stap("/root/.cape"): path = has_stap("root/.cape") else: log.warning( "Could not find STAP LKM, aborting systemtap analysis.") return False stap_start = time.time() self.proc = subprocess.Popen([ "staprun", "-vv", "-x", str(os.getpid()), "-o", "stap.log", path, ], stderr=subprocess.PIPE) while "systemtap_module_init() returned 0" not in self.proc.stderr.readline( ).decode("utf8"): pass self.proc.terminate() self.proc.wait() stap_stop = time.time() log.info("STAP aux module startup took %.2f seconds" % (stap_stop - stap_start)) return True def stop(self): try: r = self.proc.poll() log.debug("stap subprocess retval %r", r) self.proc.kill() except Exception as e: log.warning("Exception killing stap: %s", e) upload_to_host("stap.log", "stap/stap.log", False)
class STAP(Auxiliary): """system-wide syscall trace with stap.""" priority = -10 # low prio to wrap tightly around the analysis def __init__(self): self.config = Config(cfg="analysis.conf") self.fallback_strace = False def start(self): # helper function locating the stap module def has_stap(p): only_stap = [fn for fn in os.listdir(p) if fn.startswith("stap_") and fn.endswith(".ko")] if only_stap: return os.path.join(p, only_stap[0]) return False # highest priority: if the vm config specifies the path if self.config.get("analyzer_stap_path", None) and os.path.exists(self.config.get("analyzer_stap_path")): path = self.config.get("analyzer_lkm_path") # next: if a module was uploaded with the analyzer for our platform elif os.path.exists(platform.machine()) and has_stap(platform.machine()): path = has_stap(platform.machine()) # next: default path inside the machine elif os.path.exists("/root/.cuckoo") and has_stap("/root/.cuckoo"): path = has_stap("/root/.cuckoo") # next: generic module uploaded with the analyzer (single arch setup maybe?) elif has_stap("."): path = has_stap(".") else: # we can't find the stap module, fallback to strace log.warning("Could not find STAP LKM, falling back to strace.") return self.start_strace() stap_start = time.time() stderrfd = open("stap.stderr", "wb") self.proc = subprocess.Popen(["staprun", "-v", "-x", str(os.getpid()), "-o", "stap.log", path], stderr=stderrfd) # read from stderr until the tap script is compiled # while True: # if not self.proc.poll() is None: # break # line = self.proc.stderr.readline() # print "DBG LINE", line # if "Pass 5: starting run." in line: # break time.sleep(10) stap_stop = time.time() log.info("STAP aux module startup took %.2f seconds" % (stap_stop - stap_start)) return True def start_strace(self): try: os.mkdir("strace") except: pass # don't worry, it exists stderrfd = open("strace/strace.stderr", "wb") self.proc = subprocess.Popen(["strace", "-ff", "-o", "strace/straced", "-p", str(os.getpid())], stderr=stderrfd) self.fallback_strace = True return True def get_pids(self): if self.fallback_strace: return [self.proc.pid, ] return [] def stop(self): try: r = self.proc.poll() log.debug("stap subprocess retval %r", r) self.proc.kill() except Exception as e: log.warning("Exception killing stap: %s", e) if os.path.exists("stap.log"): # now upload the logfile nf = NetlogFile("logs/all.stap") fd = open("stap.log", "rb") for chunk in fd: nf.sock.sendall(chunk) # dirty direct send, no reconnecting fd.close() nf.close() # in case we fell back to strace if os.path.exists("strace"): for fn in os.listdir("strace"): # we don't need the logs from the analyzer python process itself if fn == "straced.%u" % os.getpid(): continue fp = os.path.join("strace", fn) # now upload the logfile nf = NetlogFile("logs/%s" % fn) fd = open(fp, "rb") for chunk in fd: nf.sock.sendall(chunk) # dirty direct send, no reconnecting fd.close() nf.close()
class Analyzer(object): """Cuckoo Linux Analyzer. This class handles the initialization and execution of the analysis procedure. """ def __init__(self): self.pserver = None self.config = None self.target = None def prepare(self): """Prepare env for analysis.""" # Create the folders used for storing the results. create_folders() # Initialize logging. init_logging() # Parse the analysis configuration file generated by the agent. self.config = Config(cfg="analysis.conf") if self.config.get("clock", None): # Set virtual machine clock. clock = datetime.strptime(self.config.clock, "%Y%m%dT%H:%M:%S") # Setting date and time. os.system("date -s \"{0}\"".format(clock.strftime("%y-%m-%d %H:%M:%S"))) # Initialize and start the Pipe Server. This is going to be used for # communicating with the injected and monitored processes. self.pserver = PipeServer() self.pserver.start() # We update the target according to its category. If it's a file, then # we store the path. if self.config.category == "file": self.target = os.path.join(gettempdir(), str(self.config.file_name)) # If it's a URL, well.. we store the URL. else: self.target = self.config.target def complete(self): """End analysis.""" # Dump all the notified files dump_files() # We're done! log.info("Analysis completed.") def run(self): """Run analysis. @return: operation status. """ self.prepare() log.debug("Starting analyzer from: %s", os.getcwd()) log.debug("Storing results at: %s", PATHS["root"]) log.debug("Target is: %s", self.target) # If the analysis target is a file, we choose the package according # to the file format. if self.config.category == "file": if ".bash" in self.config.file_name: arguments = ["/bin/bash", self.target] elif ".sh" in self.config.file_name: arguments = ["/bin/sh", self.target] elif ".pl" in self.config.file_name: arguments = ["/bin/perl", self.target] else: arguments = [self.target, ''] os.system("chmod +x " + str(self.target)) if self.config.options: if len(arguments) < 2: arguments.pop() arguments.append(self.config.options) else: raise CuckooError("No browser support yet") # Start file system tracer thread fstrace = FilesystemTracer() fstrace.start() # Start system call tracer thread proctrace = SyscallTracer(arguments) proctrace.start() if self.config.enforce_timeout: log.info("Enabled timeout enforce, running for the full timeout.") time_counter = 0 while True: time_counter += 1 if time_counter == int(self.config.timeout): log.info("Analysis timeout hit, terminating analysis.") break if proctrace.is_running() == False: log.info("No remaining processes. Waiting a few seconds before shutdown.") sleep(10) break # For timeout calculation sleep(1) if self.config.terminate_processes: # Try to terminate remaining active processes. We do this to make sure # that we clean up remaining open handles (sockets, files, etc.). log.info("Terminating remaining processes before shutdown.") fstrace.stop() proctrace.stop() # Let's invoke the completion procedure. self.complete() return True
class STAP(Auxiliary): """system-wide syscall trace with stap.""" priority = -10 # low prio to wrap tightly around the analysis def __init__(self): self.config = Config(cfg="analysis.conf") self.fallback_strace = False def start(self): # helper function locating the stap module def has_stap(p): files = os.listdir(p) only_stap = [fn for fn in os.listdir(p) if fn.startswith("stap_")] if only_stap: return os.path.join(p, only_stap[0]) return False # highest priority: if the vm config specifies the path if self.config.get("analyzer_stap_path", None) and os.path.exists( self.config.get("analyzer_stap_path")): path = self.config.get("analyzer_lkm_path") # next: if a module was uploaded with the analyzer for our platform elif os.path.exists(platform.machine()) and has_stap( platform.machine()): path = has_stap(platform.machine()) # next: default path inside the machine elif os.path.exists("/root/.cuckoo") and has_stap("/root/.cuckoo"): path = has_stap("/root/.cuckoo") # next: generic module uploaded with the analyzer (single arch setup maybe?) elif has_stap("."): path = has_stap(".") else: # we can't find the stap module, fallback to strace log.warning("Could not find STAP LKM, falling back to strace.") return self.start_strace() stap_start = time.time() self.proc = subprocess.Popen( ["staprun", "-v", "-x", str(os.getpid()), "-o", "stap.log", path], stderr=subprocess.PIPE) # read from stderr until the tap script is compiled # while True: # if not self.proc.poll() is None: # break # line = self.proc.stderr.readline() # print "DBG LINE", line # if "Pass 5: starting run." in line: # break time.sleep(10) stap_stop = time.time() log.info("STAP aux module startup took %.2f seconds" % (stap_stop - stap_start)) return True def start_strace(self): try: os.mkdir("strace") except: pass # don't worry, it exists stderrfd = open("strace/strace.stderr", "wb") self.proc = subprocess.Popen( ["strace", "-ff", "-o", "strace/straced", "-p", str(os.getpid())], stderr=stderrfd) self.fallback_strace = True return True def get_pids(self): if self.fallback_strace: return [ self.proc.pid, ] return [] def stop(self): try: self.proc.kill() except Exception as e: log.warning("Exception killing stap: %s", e) if os.path.exists("stap.log"): # now upload the logfile nf = NetlogFile("logs/all.stap") fd = open("stap.log", "rb") for chunk in fd: nf.sock.sendall(chunk) # dirty direct send, no reconnecting fd.close() nf.close() # in case we fell back to strace if os.path.exists("strace"): for fn in os.listdir("strace"): # we don't need the logs from the analyzer python process itself if fn == "straced.%u" % os.getpid(): continue fp = os.path.join("strace", fn) # now upload the logfile nf = NetlogFile("logs/%s" % fn) fd = open(fp, "rb") for chunk in fd: nf.sock.sendall( chunk) # dirty direct send, no reconnecting fd.close() nf.close()
class Analyzer(object): """Cuckoo Linux Analyzer. This class handles the initialization and execution of the analysis procedure. """ def __init__(self): self.pserver = None self.config = None self.target = None def prepare(self): """Prepare env for analysis.""" # Create the folders used for storing the results. create_folders() # Initialize logging. init_logging() # Parse the analysis configuration file generated by the agent. self.config = Config(cfg="analysis.conf") if self.config.get("clock", None): # Set virtual machine clock. clock = datetime.strptime(self.config.clock, "%Y%m%dT%H:%M:%S") # Setting date and time. os.system("date -s \"{0}\"".format( clock.strftime("%y-%m-%d %H:%M:%S"))) # Initialize and start the Pipe Server. This is going to be used for # communicating with the injected and monitored processes. self.pserver = PipeServer() self.pserver.start() # We update the target according to its category. If it's a file, then # we store the path. if self.config.category == "file": self.target = os.path.join(gettempdir(), str(self.config.file_name)) # If it's a URL, well.. we store the URL. else: self.target = self.config.target def complete(self): """End analysis.""" # Dump all the notified files dump_files() # We're done! log.info("Analysis completed.") def run(self): """Run analysis. @return: operation status. """ self.prepare() log.debug("Starting analyzer from: %s", os.getcwd()) log.debug("Storing results at: %s", PATHS["root"]) log.debug("Target is: %s", self.target) # If the analysis target is a file, we choose the package according # to the file format. if self.config.category == "file": if ".bash" in self.config.file_name: arguments = ["/bin/bash", self.target] elif ".sh" in self.config.file_name: arguments = ["/bin/sh", self.target] elif ".pl" in self.config.file_name: arguments = ["/bin/perl", self.target] else: arguments = [self.target, ''] os.system("chmod +x " + str(self.target)) if self.config.options: if len(arguments) < 2: arguments.pop() arguments.append(self.config.options) else: raise CuckooError("No browser support yet") # Start file system tracer thread fstrace = FilesystemTracer() fstrace.start() # Start system call tracer thread proctrace = SyscallTracer(arguments) proctrace.start() if self.config.enforce_timeout: log.info("Enabled timeout enforce, running for the full timeout.") time_counter = 0 while True: time_counter += 1 if time_counter == int(self.config.timeout): log.info("Analysis timeout hit, terminating analysis.") break if proctrace.is_running() == False: log.info( "No remaining processes. Waiting a few seconds before shutdown." ) sleep(10) break # For timeout calculation sleep(1) if self.config.terminate_processes: # Try to terminate remaining active processes. We do this to make sure # that we clean up remaining open handles (sockets, files, etc.). log.info("Terminating remaining processes before shutdown.") fstrace.stop() proctrace.stop() # Let's invoke the completion procedure. self.complete() return True