Exemplo n.º 1
0
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")
Exemplo n.º 2
0
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
Exemplo n.º 3
0
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
Exemplo n.º 4
0
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()
Exemplo n.º 5
0
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")
Exemplo n.º 6
0
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
Exemplo n.º 7
0
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)
Exemplo n.º 8
0
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()
Exemplo n.º 9
0
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
Exemplo n.º 10
0
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()
Exemplo n.º 11
0
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