Beispiel #1
0
    def __init__(self, config_path="config/config.yaml"):
        self.config_path = config_path
        conf = Confiture("config/templates/general.yaml")
        # Parse configuration file and get result
        self.config = conf.check_and_get(config_path)
        # Before we checked the config, it is considered KO
        self.config_ok = False
        # Set the log directory
        self.log_dir = self.config["log"]["path"]

        # Available pintools
        self.__pintools = dict()
        for pintool in self.config["pintool"]:
            # Check the needed two arguments
            req = ["src", "obj"]
            for r in req:
                if r not in self.config["pintool"][pintool].keys():
                    #TODO
                    raise Exception
            src = self.config["pintool"][pintool]["src"]
            obj = self.config["pintool"][pintool]["obj"]
            # Check potential extra argument
            if "prev_step" in self.config["pintool"][pintool].keys():
                prev_step = self.config["pintool"][pintool]["prev_step"]
            else:
                prev_step = None
            # Create pintool object
            pintool_obj = Pintool(
                                    name=pintool,
                                    src_path=src,
                                    obj_path=obj,
                                    pinconf=self.config["pin"],
                                    stdout=self.out,
                                    stderr=self.out,
                                    log_dir=self.log_dir,
                                    prev_step=prev_step,
                                )
            self.__pintools[pintool] = pintool_obj

        # Create a test object
        # Testing options
        kwargs = dict()
        kwargs["log"] = self.out
        if "test" in self.config.keys() and "proto" in self.config["test"]:
            kwargs["proto"] = self.config["test"]["proto"]
        self.test = ScatTest(**kwargs)

        # Init shell
        Cmd.__init__(self, completekey='tab')
Beispiel #2
0
class ScatShell(Cmd):

    prompt = 'scat > '

    def __init__(self, config_path="config/config.yaml"):
        self.config_path = config_path
        conf = Confiture("config/templates/general.yaml")
        # Parse configuration file and get result
        self.config = conf.check_and_get(config_path)
        # Before we checked the config, it is considered KO
        self.config_ok = False
        # Set the log directory
        self.log_dir = self.config["log"]["path"]

        # Available pintools
        self.__pintools = dict()
        for pintool in self.config["pintool"]:
            # Check the needed two arguments
            req = ["src", "obj"]
            for r in req:
                if r not in self.config["pintool"][pintool].keys():
                    #TODO
                    raise Exception
            src = self.config["pintool"][pintool]["src"]
            obj = self.config["pintool"][pintool]["obj"]
            # Check potential extra argument
            if "prev_step" in self.config["pintool"][pintool].keys():
                prev_step = self.config["pintool"][pintool]["prev_step"]
            else:
                prev_step = None
            # Create pintool object
            pintool_obj = Pintool(
                                    name=pintool,
                                    src_path=src,
                                    obj_path=obj,
                                    pinconf=self.config["pin"],
                                    stdout=self.out,
                                    stderr=self.out,
                                    log_dir=self.log_dir,
                                    prev_step=prev_step,
                                )
            self.__pintools[pintool] = pintool_obj

        # Create a test object
        # Testing options
        kwargs = dict()
        kwargs["log"] = self.out
        if "test" in self.config.keys() and "proto" in self.config["test"]:
            kwargs["proto"] = self.config["test"]["proto"]
        self.test = ScatTest(**kwargs)

        # Init shell
        Cmd.__init__(self, completekey='tab')

    def emptyline(self):
        pass

    #========== LOG functions ==========#

    def out(self, msg, verbose=True, crlf=True):
        """
            Print message on standard input, with formatting.

            @param msg  message to print

        """
        if verbose:
            sys.stdout.write("[*] " + msg)
            if crlf:
                sys.stdout.write("\n")

    def stderr(self, msg):
        """
            Print message on standard error, with formatting.

            @param msg  message to print

        """
        sys.stderr.write("*** " + msg + "\n")

    #========== Check functions ==========#

    def __check_path(self, fpath, **kwargs):
        """
            Perform some verifications of a path (e.g. exists? is a directory?)

            @param fpath            path of the file/dir to check

            @param (opt) isdir      True => check if fpath is directory

            @param (opt) isexec     True => check if fpath is executable

            @raise ValueError if some error occur

        """
        # By default, file is not required to be a directory
        isdir = False
        if "isdir" in kwargs.keys():
            isdir = kwargs["isdir"]

        # By default, file is not required to be executable
        isexec = False
        if "isexec" in kwargs.keys():
            isexec = kwargs["isexec"]

        # Check if path is not empty
        if fpath == "":
            self.stderr("You must specify a path")
            raise ValueError

        # If executable, check if we can execute (exists + permission X)
        if isexec and subprocess.call("type " + fpath, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) != 0:
            self.stderr("Specified target ({0}) is not found or not executable - check permissions".format(fpath))
            raise ValueError
        else:
            return

        # Check path existance
        if not os.path.exists(fpath):
            self.stderr("Specified target ({0}) does not exist".format(fpath))
            raise ValueError

        # If dir is mandatory, perform additional check
        if isdir and not os.path.isdir(fpath):
            self.stderr("Specified target ({0}) is not a directory".format(fpath))
            raise ValueError

    #========== Completion functions ==========#

    def __complete_bin(self, text, line, begidx, endidx):
        paths = list()
        for path in glob.glob("/usr/bin/" + text + "*"):
            if os.path.isdir(path):
                continue
            paths.append(path.replace("/usr/bin/", ""))
        for path in glob.glob("/bin/" + text + "*"):
            if os.path.isdir(path):
                continue
            paths.append(path.replace("/bin/", ""))
        return paths

    def __get_pgm_list(self, inf_code=None):
        file_list = [f for f in os.listdir(self.log_dir) if f.endswith("log")]
        pgm_list = set([re.sub("_.*", "", f) for f in file_list])
        pgm_inf = list()
        for p in pgm_list:
            inf = set([re.sub("_.*$", "", re.sub("^[^_]*_", "", f)) for f in file_list if f.find(p) >= 0])
            pgm_inf.append((p, list(inf)))
        return pgm_inf

    def __get_pgm_and_inf(self, s):
        args = s.split(" ")
        if len(args) == 0 or args[0] == '':
            for p, inf in self.__get_pgm_list():
                print(p)
            raise ValueError
        pgm = args[0]
        if len(args) == 1:
            for p, inf in self.__get_pgm_list():
                if p != pgm:
                    continue
                for i in inf:
                    print(i)
                raise ValueError
        # This line might raise KeyError if pintool is not found
        # This exception should be handled by the caller
        pintool = self.__pintools[args[1]]
        return pgm, pintool

    def __complete_path(self, text, line, begidx, endidx):
        before_arg = line.rfind(" ", 0, begidx)
        if before_arg == -1:
            return
        fixed = line[before_arg+1:begidx]
        arg = line[before_arg+1:endidx]
        pattern = arg + "*"
        paths = list()
        for path in glob.glob(pattern):
            if os.path.isdir(path) and path[-1] != os.sep:
                path += os.sep
            paths.append(path.replace(fixed, "", 1))
        return paths

    #========== checkconfig ==========#

    def help_checkconfig(self):
        print(self.do_checkconfig.__doc__.replace("\n", ""))

    def do_checkconfig(self, s):
        """
            Check the configuration file, and in particular:
                - the log directory (path, permissions)
                - pin (path, permissions)

        """
        #TODO check also pintool paths => call pin.check_config
        try:
            self.__check_path(self.log_dir, isdir=True)
            self.__check_path(self.config["pin"]["bin"], isexec=True)
        except ValueError:
            self.config_ok = False
            return
        self.config_ok = True

    #========== setlogdir ==========#

    def help_setlogdir(self):
        print(self.do_setlogdir.__doc__.replace("\n", ""))

    def do_setlogdir(self, directory):
        """
            Specify path to log directory. Target must exists.

        """
        try:
            self.__check_path(directory, isdir=False)
        except ValueError:
            return
        self.log_dir = directory

    #========== make ==========#

    def help_make(self):
        print(self.do_make.__doc__)

    def do_make(self, s):
        """
            (Re)compile pintools. With no argument, this command recompiles
            every pintool registered.

            You can also specify the pintools you want to compile (e.g. make arity type)

        """
        force = False
        debug = False
        trace = False

        if s != "":
            args = s.split(" ")
        else:
            args = list()

        to_compile = list()

        for arg in args:
            if arg == '-f' or arg == '--force' or arg == '-B':
                force = True
                continue
            if arg == '-d' or arg == '--debug':
                debug = True
                continue
            if arg == '-t' or arg == '--trace':
                trace = True
                continue

            try:
                p = self.__pintools[arg]
            except KeyError:
                self.stderr("pintool {0} is unknown".format(arg))
                return
            to_compile.append(p)

        # If no pintool given in argument, compile everything
        if len(to_compile) == 0:
            to_compile = self.__pintools.values()

        # Compile pintools
        for p in to_compile:
            p.compile(force, debug, trace)

    #========== display ==========#

    def help_display(self):
        print(self.do_display.__doc__.replace("\n", ""))

    def complete_display(self, text, line, begidx, endidx):
        pgm_inf  = self.__get_pgm_list()
        for p, inf in pgm_inf:
            if line.find(p) >= 0:
                return [i for i in inf if i.startswith(text)]
        return [pgm for pgm, inf in pgm_inf if pgm.startswith(text)]

    def do_display(self, s):
        """
            Display results of inference

        """
        try:
            pgm, pintool = self.__get_pgm_and_inf(s)
        except ValueError:
            return
        except KeyError:
            #TODO explicit message (w/ pintool and binary details)
            self.stderr("Pintool error")
            return

        pintool.get_analysis(pgm).display()

    #========== parsedata ==========#

    def help_parsedata(self):
        print(self.do_parsedata.__doc__.replace("\n", ""))

    def do_parsedata(self, s):
        """
            Parse source code to test inference results

        """
        # TODO check number of args
        # TODO completion on args
        split = s.split(" ")
        if len(split) == 1:
            binary, srcdir = split[0], None
        else:
            binary, srcdir = split

        pgm = os.path.basename(binary)

        # Check CLANG configuration
        conf = Confiture("config/templates/clang.yaml")
        conf.check("config/config.yaml")
        # Create a parser object
        data = Data(self.config["clang"]["data-path"], pgm)
        data.parse(binary, self.config["clang"]["lib-path"], srcdir)
        data.dump()

    #========== testing ==========#

    def help_test(self):
        print(self.do_test.__doc__.replace("\n", ""))

    def complete_test(self, text, line, begidx, endidx):
        # TODO
        pass

    def do_test(self, s):
        # TODO documentation
        # TODO check that config is specified in config file (+template)
        self.test.proto([self.__pintools["arity"], self.__pintools["type"]])

    #========== accuracy ==========#

    def help_accuracy(self):
        print(self.do_accuracy.__doc__.replace("\n", ""))

    def complete_accuracy(self, text, line, begidx, endidx):
        return self.complete_display(text, line, begidx, endidx)

    def do_accuracy(self, s):
        """
            Analyse the results of inference for a given program,
            by comparison with binary and source code.

        """
        try:
            pgm, pintool = self.__get_pgm_and_inf(s)
        except ValueError as e:
            raise e
        except KeyError:
            self.stderr("Pintool error")
            return
        # Check CLANG configuration
        conf = Confiture("config/templates/clang.yaml")
        conf.check("config/config.yaml")
        try:
            data = Data(self.config["clang"]["data-path"], pgm)
            data.load()
        except IOError:
            data = None
        if data is None:
            self.stderr("error: you must parse source code of \"{0}\" first (use parsedata)".format(pgm))
            return
        pintool.get_analysis(pgm, data).accuracy()

    #========== mismatch ==========#

    def help_mismatch(self):
        print(self.do_accuracy.__doc__.replace("\n", ""))

    def complete_mismatch(self, text, line, begidx, endidx):
        return self.complete_display(text, line, begidx, endidx)

    def do_mismatch(self, s):
        """
            Displays all mismatch for a given program,
            by comparison with binary and source code.

        """
        try:
            pgm, pintool = self.__get_pgm_and_inf(s)
        except ValueError:
            return
        except KeyError:
            #TODO explicit message (w/ pintool and binary details)
            self.stderr("Pintool error")

        # Check CLANG configuration
        conf = Confiture("config/templates/clang.yaml")
        conf.check("config/config.yaml")
        try:
            data = Data(self.config["clang"]["data-path"], pgm)
            data.load()
        except IOError:
            data = None
        if data is None:
            self.stderr("error: you must parse source code of \"{0}\" first (use parsedata)".format(pgm))
            return

        pintool.get_analysis(pgm, data).mismatch()

    #========== new pintool ==========#

    def complete_launch(self, text, line, begidx, endidx):
        if len(line.split(" ")) < 3:
            return filter(
                            lambda x: x.startswith(text),
                            map(lambda x: str(x), self.__pintools),
                        )
        elif len(line.split(" ")) < 4:
            return self.__complete_bin(text, line, begidx, endidx)
        else:
            return  self.__complete_path(text, line, begidx, endidx)

    def do_launch(self, s):
        split = s.split(" ")
        index = 0

        release = False
        force = False
        debug = False
        trace = False
        while index < len(split) and split[index].startswith("-"):
            arg = split[index]
            if arg == '-f' or arg == '--force' or arg == '-B':
                force = True
            elif arg == '-r' or arg == '--release':
                release = True
            elif arg == '-d' or arg == '--debug':
                debug = True
            elif arg == '-t' or arg == '--trace':
                trace = True
            index += 1

        inf = split[index]
        index += 1

        if inf not in self.__pintools.keys():
            print('Unknown inference "{}"'.format(inf))
            return

        p = self.__pintools[inf]
        if release or debug or trace:
            if not p.compile(force, debug, trace):
                return

        # Before inference, check that the configuration is correct
        self.do_checkconfig("")
        if not self.config_ok:
            return
        # Parse command into binary + args
        args =  list()
        for i, arg in enumerate(split[index:]):
            if arg[0] == "\"":
                arg = arg[1:]
            if arg[-1] == "\"":
                arg = arg[:-1]
            if i == 0:
                binary = arg
            else:
                args.append(arg)
        # Check the binary (exists? is executable?)
        try:
            self.__check_path(binary, isdir=False, isexec=True)
        except ValueError:
            return
        # Run inference
        self.out("Launching {0} inference on {1}".format(p, binary))
        p.launch(binary, args)

    #========== ALLOC RETRIEVING ==========

    def do_memcomb(self, s):
        # Get log file from last block inference
        if "memblock" not in self.__pintools.keys():
            self.stderr("you must run memblock inference first")
            return
        proto_logfile = self.__pintools["memblock"].get_logfile(s, prev=True)
        mem_logfile = self.__pintools["memblock"].get_logfile(s, prev=False)
        MemComb(mem_logfile, proto_logfile, self.out).run()

    #========== COUPLE FROM MEMBLOCK ==========

    def do_couple(self, s):
        # Get log file from last block inference
        if "memblock" not in self.__pintools.keys():
            self.stderr("you must run memblock inference first")
            return
        logfile = self.__pintools["memblock"].get_logfile(s, prev=False)
        Couple(logfile, self.out).run()

    #========== DETECT SIMPLIFIED UAF ==========
    def do_memuaf(self, s):
        pgm = s.split(" ")[0]
        # Get log file from last block inference
        if "memblock" not in self.__pintools.keys():
            self.stderr("you must run memblock inference first")
            return
        proto_logfile = self.__pintools["memblock"].get_logfile(pgm, prev=True)
        mem_logfile = self.__pintools["memblock"].get_logfile(pgm, prev=False)
        if len(s.split(" ")) > 1:
            ALLOC, FREE = s.split(" ")[1:3]
        else:
            ALLOC, FREE = MemComb(mem_logfile, proto_logfile, self.out).run(wrappers=False)
        MemUAF(mem_logfile, self.out).run(ALLOC, FREE)