class FSTimeline:
    def __init__(self, disk):
        self._disk = disk
        self._filesystem = None
        self._filetype_cache = {}
        self._checksum_cache = {}
        self.logger = logging.getLogger(
            "%s.%s" % (self.__module__, self.__class__.__name__))

    def __enter__(self):
        self._filesystem = FileSystem(self._disk)
        self._filesystem.mount()

        return self

    def __exit__(self, *_):
        self._filesystem.umount()

    def __getattr__(self, attr):
        return getattr(self._filesystem, attr)

    def timeline(self):
        self.logger.debug("Extracting File System timeline events.")
        events = tuple(
            Event(d.inode, d.path, d.size, d.allocated, t, r)
            for d in self._visit_filesystem()
            for t, r in ((d.atime, 'access'), (d.mtime, 'change'),
                         (d.ctime, 'attribute_change'), (d.crtime, 'creation'))
            if t > 0)

        self.logger.debug("Sorting File System timeline events.")
        return sorted(events, key=lambda e: e.timestamp)

    @lru_cache(maxsize=None)
    def file(self, path):
        """Identifies the file type.

        Caches the result to reduce overhead on duplicated events.

        """
        return self._filesystem.file(path)

    @lru_cache(maxsize=None)
    def checksum(self, path):
        """Identifies the file type.

        Caches the result to reduce overhead on duplicated events.

        """
        return self._filesystem.checksum(path)

    def _visit_filesystem(self):
        """Walks through the filesystem content."""
        self.logger.debug("Parsing File System content.")

        root_partition = self._filesystem.inspect_get_roots()[0]

        yield from self._root_dirent()

        for entry in self._filesystem.filesystem_walk(root_partition):
            yield Dirent(
                entry['tsk_inode'],
                self._filesystem.path('/' + entry['tsk_name']),
                entry['tsk_size'], entry['tsk_type'],
                True if entry['tsk_flags'] & TSK_ALLOC else False,
                timestamp(entry['tsk_atime_sec'], entry['tsk_atime_nsec']),
                timestamp(entry['tsk_mtime_sec'], entry['tsk_mtime_nsec']),
                timestamp(entry['tsk_ctime_sec'], entry['tsk_ctime_nsec']),
                timestamp(entry['tsk_crtime_sec'], entry['tsk_crtime_nsec']))

    def _root_dirent(self):
        """Returns the root folder dirent as filesystem_walk API doesn't."""
        fstat = self._filesystem.stat('/')

        yield Dirent(fstat['ino'], self._filesystem.path('/'), fstat['size'],
                     'd', True, timestamp(fstat['atime'], 0),
                     timestamp(fstat['mtime'],
                               0), timestamp(fstat['ctime'], 0), 0)
Beispiel #2
0
class VTScanner:
    """VirusTotal scanner.

    Allows to scan the given disk content and query VirusTotal.

    disk must contain the path of a valid disk image.
    apikey must be a valid VT API key.

    The attribute batchsize controls the amount of object per VT query.

    """
    def __init__(self, disk, apikey):
        self._disk = disk
        self._apikey = apikey
        self._filesystem = None
        self.batchsize = 1
        self.logger = logging.getLogger(
            "%s.%s" % (self.__module__, self.__class__.__name__))

    def __enter__(self):
        self._filesystem = FileSystem(self._disk)
        self._filesystem.mount()

        return self

    def __exit__(self, *_):
        self._filesystem.umount()

    def __getattr__(self, attr):
        return getattr(self._filesystem, attr)

    @property
    def apikey(self):
        return self._apikey

    def scan(self, filetypes=None):
        """Iterates over the content of the disk and queries VirusTotal
        to determine whether it's malicious or not.

        filetypes is a list containing regular expression patterns.
        If given, only the files which type will match with one or more of
        the given patterns will be queried against VirusTotal.

        For each file which is unknown by VT or positive to any of its engines,
        the method yields a namedtuple:

        VTReport(path        -> C:\\Windows\\System32\\infected.dll
                 hash        -> ab231...
                 detections) -> dictionary engine -> detection

        Files unknown by VirusTotal will contain the string 'unknown'
        in the detection field.

        """
        self.logger.debug("Scanning FS content.")
        checksums = self.filetype_filter(self._filesystem.checksums('/'),
                                         filetypes=filetypes)

        self.logger.debug("Querying %d objects to VTotal.", len(checksums))

        for files in chunks(checksums, size=self.batchsize):
            files = dict((reversed(e) for e in files))
            response = vtquery(self._apikey, files.keys())

            yield from self.parse_response(files, response)

    def filetype_filter(self, files, filetypes=None):
        if filetypes is not None:
            return [
                f for f in files
                if any((re.match(t, self._filesystem.file(f[0]))
                        for t in filetypes))
            ]
        else:
            return files

    def parse_response(self, files, response):
        response = isinstance(response, list) and response or [response]

        for result in response:
            yield from self.parse_result(result, files)

    def parse_result(self, result, files):
        sha1 = result['resource']
        path = files[sha1]

        if result['response_code'] > 0:
            positives = result['positives']

            self.logger.debug("%s - %d positives.", path, positives)

            if positives > 0:
                detections = {
                    engine: detection
                    for engine, detection in result['scans'].items()
                    if detection['detected']
                }

                yield VTReport(path, sha1, detections)
        else:
            self.logger.debug("%s - Unknown file.", path)

            yield VTReport(path, sha1, 'UNKNOWN')
Beispiel #3
0
class FSTimeline:
    def __init__(self, disk):
        self._disk = disk
        self._filesystem = None
        self._filetype_cache = {}
        self._checksum_cache = {}
        self.logger = logging.getLogger(
            "%s.%s" % (self.__module__, self.__class__.__name__))

    def __enter__(self):
        self._filesystem = FileSystem(self._disk)
        self._filesystem.mount()

        return self

    def __exit__(self, *_):
        self._filesystem.umount()

    def __getattr__(self, attr):
        return getattr(self._filesystem, attr)

    def timeline(self):
        self.logger.debug("Extracting File System timeline events.")
        events = tuple(Event(d.inode, d.path, d.size, d.allocated, t, r)
                       for d in self._visit_filesystem()
                       for t, r in ((d.atime, 'access'),
                                    (d.mtime, 'change'),
                                    (d.ctime, 'attribute_change'),
                                    (d.crtime, 'creation'))
                       if t > 0)

        self.logger.debug("Sorting File System timeline events.")
        return sorted(events, key=lambda e: e.timestamp)

    @lru_cache(maxsize=None)
    def file(self, path):
        """Identifies the file type.

        Caches the result to reduce overhead on duplicated events.

        """
        return self._filesystem.file(path)

    @lru_cache(maxsize=None)
    def checksum(self, path):
        """Identifies the file type.

        Caches the result to reduce overhead on duplicated events.

        """
        return self._filesystem.checksum(path)

    def _visit_filesystem(self):
        """Walks through the filesystem content."""
        self.logger.debug("Parsing File System content.")

        root_partition = self._filesystem.inspect_get_roots()[0]

        yield from self._root_dirent()

        for entry in self._filesystem.filesystem_walk(root_partition):
            yield Dirent(
                entry['tsk_inode'],
                self._filesystem.path('/' + entry['tsk_name']),
                entry['tsk_size'], entry['tsk_type'],
                True if entry['tsk_flags'] & TSK_ALLOC else False,
                timestamp(entry['tsk_atime_sec'], entry['tsk_atime_nsec']),
                timestamp(entry['tsk_mtime_sec'], entry['tsk_mtime_nsec']),
                timestamp(entry['tsk_ctime_sec'], entry['tsk_ctime_nsec']),
                timestamp(entry['tsk_crtime_sec'], entry['tsk_crtime_nsec']))

    def _root_dirent(self):
        """Returns the root folder dirent as filesystem_walk API doesn't."""
        fstat = self._filesystem.stat('/')

        yield Dirent(fstat['ino'], self._filesystem.path('/'),
                     fstat['size'], 'd', True,
                     timestamp(fstat['atime'], 0),
                     timestamp(fstat['mtime'], 0),
                     timestamp(fstat['ctime'], 0),
                     0)
Beispiel #4
0
class VTScanner:
    """VirusTotal scanner.

    Allows to scan the given disk content and query VirusTotal.

    disk must contain the path of a valid disk image.
    apikey must be a valid VT API key.

    The attribute batchsize controls the amount of object per VT query.

    """
    def __init__(self, disk, apikey):
        self._disk = disk
        self._apikey = apikey
        self._filesystem = None
        self.batchsize = 1
        self.logger = logging.getLogger(
            "%s.%s" % (self.__module__, self.__class__.__name__))

    def __enter__(self):
        self._filesystem = FileSystem(self._disk)
        self._filesystem.mount()

        return self

    def __exit__(self, *_):
        self._filesystem.umount()

    def __getattr__(self, attr):
        return getattr(self._filesystem, attr)

    @property
    def apikey(self):
        return self._apikey

    def scan(self, filetypes=None):
        """Iterates over the content of the disk and queries VirusTotal
        to determine whether it's malicious or not.

        filetypes is a list containing regular expression patterns.
        If given, only the files which type will match with one or more of
        the given patterns will be queried against VirusTotal.

        For each file which is unknown by VT or positive to any of its engines,
        the method yields a namedtuple:

        VTReport(path        -> C:\\Windows\\System32\\infected.dll
                 hash        -> ab231...
                 detections) -> dictionary engine -> detection

        Files unknown by VirusTotal will contain the string 'unknown'
        in the detection field.

        """
        self.logger.debug("Scanning FS content.")
        checksums = self.filetype_filter(self._filesystem.checksums('/'),
                                         filetypes=filetypes)

        self.logger.debug("Querying %d objects to VTotal.", len(checksums))

        for files in chunks(checksums, size=self.batchsize):
            files = dict((reversed(e) for e in files))
            response = vtquery(self._apikey, files.keys())

            yield from self.parse_response(files, response)

    def filetype_filter(self, files, filetypes=None):
        if filetypes is not None:
            return [f for f in files
                    if any((re.match(t, self._filesystem.file(f[0]))
                            for t in filetypes))]
        else:
            return files

    def parse_response(self, files, response):
        response = isinstance(response, list) and response or [response]

        for result in response:
            yield from self.parse_result(result, files)

    def parse_result(self, result, files):
        sha1 = result['resource']
        path = files[sha1]

        if result['response_code'] > 0:
            positives = result['positives']

            self.logger.debug("%s - %d positives.", path, positives)

            if positives > 0:
                detections = {engine: detection for engine, detection
                              in result['scans'].items()
                              if detection['detected']}

                yield VTReport(path, sha1, detections)
        else:
            self.logger.debug("%s - Unknown file.", path)

            yield VTReport(path, sha1, 'UNKNOWN')