def load_disktype_data(self):
        """Calls the :command:`disktype` command and obtains the disk GUID from GPT volume systems. As we
        are running the tool anyway, the label is also extracted from the tool if it is not yet set.

        The disktype data is only loaded and not assigned to volumes yet.
        """

        if not _util.command_exists('disktype'):
            logger.warning("disktype not installed, could not detect volume type")
            return None

        disktype = _util.check_output_(['disktype', self.parent.get_raw_path()]).strip()

        current_partition = None
        for line in disktype.splitlines():
            if not line:
                continue
            # noinspection PyBroadException
            try:
                line = line.strip()

                find_partition_nr = re.match(r"^Partition (\d+):", line)
                if find_partition_nr:
                    current_partition = int(find_partition_nr.group(1))
                elif current_partition is not None:
                    if line.startswith("Type ") and "GUID" in line:
                        self._disktype[current_partition]['guid'] = \
                            line[line.index('GUID') + 5:-1].strip()  # output is between ()
                    elif line.startswith("Partition Name "):
                        self._disktype[current_partition]['label'] = \
                            line[line.index('Name ') + 6:-1].strip()  # output is between ""
            except Exception:
                logger.exception("Error while parsing disktype output")
                return
Beispiel #2
0
    def _determine_auto_detection_method():
        """Return the detection method to use when the detection method is 'auto'"""

        if _util.module_exists('pytsk3'):
            return 'pytsk3'
        elif _util.command_exists('mmls'):
            return 'mmls'
        else:
            return 'parted'
    def _determine_auto_detection_method():
        """Return the detection method to use when the detection method is 'auto'"""

        if _util.module_exists('pytsk3'):
            return 'pytsk3'
        elif _util.command_exists('mmls'):
            return 'mmls'
        else:
            return 'parted'
Beispiel #4
0
    def carve(self, freespace=True):
        """Call this method to carve the free space of the volume for (deleted) files. Note that photorec has its
        own interface that temporarily takes over the shell.

        :param freespace: indicates whether the entire volume should be carved (False) or only the free space (True)
        :type freespace: bool
        :return: string to the path where carved data is available
        :raises CommandNotFoundError: if the underlying command does not exist
        :raises SubsystemError: if the underlying command fails
        :raises NoMountpointAvailableError: if there is no mountpoint available
        :raises NoLoopbackAvailableError: if there is no loopback available (only when volume has no slot number)
        """

        if not _util.command_exists('photorec'):
            logger.warning("photorec is not installed, could not carve volume")
            raise CommandNotFoundError("photorec")

        self._make_mountpoint(var_name='carve', suffix="carve", in_paths=True)

        # if no slot, we need to make a loopback that we can use to carve the volume
        loopback_was_created_for_carving = False
        if not self.slot:
            if not self.loopback:
                self._find_loopback()
                #Can't carve if volume has no slot number and can't be mounted on loopback.
                loopback_was_created_for_carving = True

            # noinspection PyBroadException
            try:
                _util.check_call_(["photorec", "/d", self._paths['carve'] + os.sep, "/cmd", self.loopback,
                                  ("freespace," if freespace else "") + "search"])

                # clean out the loop device if we created it specifically for carving
                if loopback_was_created_for_carving:
                    # noinspection PyBroadException
                    try:
                        _util.check_call_(['losetup', '-d', self.loopback])
                    except Exception:
                        pass
                    else:
                        self.loopback = ""

                return self._paths['carve']
            except Exception as e:
                logger.exception("Failed carving the volume.")
                raise SubsystemError(e)
        else:
            # noinspection PyBroadException
            try:
                _util.check_call_(["photorec", "/d", self._paths['carve'] + os.sep, "/cmd", self.get_raw_path(),
                                  str(self.slot) + (",freespace" if freespace else "") + ",search"])
                return self._paths['carve']

            except Exception as e:
                logger.exception("Failed carving the volume.")
                raise SubsystemError(e)
Beispiel #5
0
    def carve(self, freespace=True):
        """Call this method to carve the free space of the volume for (deleted) files. Note that photorec has its
        own interface that temporarily takes over the shell.

        :param freespace: indicates whether the entire volume should be carved (False) or only the free space (True)
        :type freespace: bool
        :return: string to the path where carved data is available
        :raises CommandNotFoundError: if the underlying command does not exist
        :raises SubsystemError: if the underlying command fails
        :raises NoMountpointAvailableError: if there is no mountpoint available
        :raises NoLoopbackAvailableError: if there is no loopback available (only when volume has no slot number)
        """

        if not _util.command_exists('photorec'):
            logger.warning("photorec is not installed, could not carve volume")
            raise CommandNotFoundError("photorec")

        self._make_mountpoint(var_name='carve', suffix="carve", in_paths=True)

        # if no slot, we need to make a loopback that we can use to carve the volume
        loopback_was_created_for_carving = False
        if not self.slot:
            if not self.loopback:
                self._find_loopback()
                #Can't carve if volume has no slot number and can't be mounted on loopback.
                loopback_was_created_for_carving = True

            # noinspection PyBroadException
            try:
                _util.check_call_(["photorec", "/d", self._paths['carve'] + os.sep, "/cmd", self.loopback,
                                  ("freespace," if freespace else "") + "search"])

                # clean out the loop device if we created it specifically for carving
                if loopback_was_created_for_carving:
                    # noinspection PyBroadException
                    try:
                        _util.check_call_(['losetup', '-d', self.loopback])
                    except Exception:
                        pass
                    else:
                        self.loopback = ""

                return self._paths['carve']
            except Exception as e:
                logger.exception("Failed carving the volume.")
                raise SubsystemError(e)
        else:
            # noinspection PyBroadException
            try:
                _util.check_call_(["photorec", "/d", self._paths['carve'] + os.sep, "/cmd", self.get_raw_path(),
                                  str(self.slot) + (",freespace" if freespace else "") + ",search"])
                return self._paths['carve']

            except Exception as e:
                logger.exception("Failed carving the volume.")
                raise SubsystemError(e)
Beispiel #6
0
 def _check_command(self, command, package="", why=""):
     if _util.command_exists(command):
         print(" INSTALLED {}".format(command))
     elif why and package:
         print(" MISSING   {:<20}needed for {}, part of the {} package".format(command, why, package))
     elif why:
         print(" MISSING   {:<20}needed for {}".format(command, why))
     elif package:
         print(" MISSING   {:<20}part of the {} package".format(command, package))
     else:
         print(" MISSING   {}".format(command))
Beispiel #7
0
    def carve(self, freespace=True):
        """Call this method to carve the free space of the volume for (deleted) files. Note that photorec has its
        own interface that temporarily takes over the shell.

        :param freespace: indicates whether the entire volume should be carved (False) or only the free space (True)
        :type freespace: bool
        :return: boolean indicating whether the command succeeded
        """

        if not _util.command_exists('photorec'):
            logger.warning("photorec is not installed, could not carve volume")
            return False

        if not self._make_mountpoint(var_name='carvepoint', suffix="carve"):
            return False

        # if no slot, we need to make a loopback that we can use to carve the volume
        loopback_was_created_for_carving = False
        if not self.slot:
            if not self.loopback:
                if not self._find_loopback():
                    logger.error("Can't carve if volume has no slot number and can't be mounted on loopback.")
                    return False
                loopback_was_created_for_carving = True

            try:
                _util.check_call_(["photorec", "/d", self.carvepoint + os.sep, "/cmd", self.loopback,
                                  ("freespace," if freespace else "") + "search"])

                # clean out the loop device if we created it specifically for carving
                if loopback_was_created_for_carving:
                    try:
                        _util.check_call_(['losetup', '-d', self.loopback])
                    except Exception:
                        pass
                    else:
                        self.loopback = ""

                return True
            except Exception:
                logger.exception("Failed carving the volume.")
                return False
        else:
            try:
                _util.check_call_(["photorec", "/d", self.carvepoint + os.sep, "/cmd", self.get_raw_base_path(),
                                  str(self.slot) + (",freespace" if freespace else "") + ",search"])
                return True

            except Exception:
                logger.exception("Failed carving the volume.")
                return False
Beispiel #8
0
    def vshadowmount(self):
        """Method to call vshadowmount and mount NTFS volume shadow copies.

        :return: string representing the path to the volume shadow copies
        :raises CommandNotFoundError: if the underlying command does not exist
        :raises SubSystemError: if the underlying command fails
        :raises NoMountpointAvailableError: if there is no mountpoint available
        """

        if not _util.command_exists('vshadowmount'):
            logger.warning("vshadowmount is not installed, could not mount volume shadow copies")
            raise CommandNotFoundError('vshadowmount')

        self._make_mountpoint(var_name='vss', suffix="vss", in_paths=True)

        try:
            _util.check_call_(["vshadowmount", "-o", str(self.offset), self.get_raw_path(), self._paths['vss']])
            return self._paths['vss']
        except Exception as e:
            logger.exception("Failed mounting the volume shadow copies.")
            raise SubsystemError(e)
Beispiel #9
0
    def is_raid(self):
        """Tests whether this image (was) part of a RAID array. Requires :command:`mdadm` to be installed."""

        if not _util.command_exists('mdadm'):
            logger.info("mdadm not installed, could not detect RAID")
            return False

        # Scan for new lvm volumes
        # noinspection PyBroadException
        try:
            result = _util.check_output_(["mdadm", "--examine", self.get_raw_path()], stderr=subprocess.STDOUT)
            for l in result.splitlines():
                if 'Raid Level' in l:
                    logger.debug("Detected RAID level " + l[l.index(':') + 2:])
                    break
            else:
                return False
        except Exception:
            return False

        return True
Beispiel #10
0
    def detect_volume_shadow_copies(self):
        """Method to call vshadowmount and mount NTFS volume shadow copies.

        :return: iterable with the :class:`Volume` objects of the VSS
        :raises CommandNotFoundError: if the underlying command does not exist
        :raises SubSystemError: if the underlying command fails
        :raises NoMountpointAvailableError: if there is no mountpoint available
        """

        if not _util.command_exists('vshadowmount'):
            logger.warning("vshadowmount is not installed, could not mount volume shadow copies")
            raise CommandNotFoundError('vshadowmount')

        self._make_mountpoint(var_name='vss', suffix="vss", in_paths=True)

        try:
            _util.check_call_(["vshadowmount", "-o", str(self.offset), self.get_raw_path(), self._paths['vss']])
        except Exception as e:
            logger.exception("Failed mounting the volume shadow copies.")
            raise SubsystemError(e)
        else:
            return self.volumes.detect_volumes(vstype='vss')
Beispiel #11
0
    def _load_disktype_data(self):
        """Calls the :command:`disktype` command and obtains the disk GUID from GPT volume systems. As we
        are running the tool anyway, the label is also extracted from the tool if it is not yet set.

        The disktype data is only loaded and not assigned to volumes yet.
        """

        if not _util.command_exists('disktype'):
            logger.warning(
                "disktype not installed, could not detect volume type")
            return None

        disktype = _util.check_output_(
            ['disktype', self.parent.get_raw_path()]).strip()

        current_partition = None
        for line in disktype.splitlines():
            if not line:
                continue
            # noinspection PyBroadException
            try:
                line = line.strip()

                find_partition_nr = re.match(r"^Partition (\d+):", line)
                if find_partition_nr:
                    current_partition = int(find_partition_nr.group(1))
                elif current_partition is not None:
                    if line.startswith("Type ") and "GUID" in line:
                        self._disktype[current_partition]['guid'] = \
                            line[line.index('GUID') + 5:-1].strip()  # output is between ()
                    elif line.startswith("Partition Name "):
                        self._disktype[current_partition]['label'] = \
                            line[line.index('Name ') + 6:-1].strip()  # output is between ""
            except Exception:
                logger.exception("Error while parsing disktype output")
                return
Beispiel #12
0
def main():
    class MyParser(argparse.ArgumentParser):
        def error(self, message):
            sys.stderr.write('error: {0}\n'.format(message))
            self.print_help()
            sys.exit(2)

    parser = MyParser(
        description='Utility to mount volumes in Encase and dd images locally.'
    )
    parser.add_argument(
        'images',
        nargs='*',
        help=
        'path(s) to the image(s) that you want to mount; generally just the first file (e.g. '
        'the .E01 or .001 file) or the folder containing the files is enough in the case of '
        'split files')

    # Special options
    parser.add_argument('--version',
                        action='version',
                        version=__version__,
                        help='display version and exit')
    parser.add_argument(
        '--check',
        action=CheckAction,
        nargs=0,
        help='do a system check and list which tools are installed')
    parser.add_argument('-i',
                        '--interactive',
                        action='store_true',
                        default=False,
                        help='enter the interactive shell')

    # Utility specific
    parser.add_argument(
        '-u',
        '--unmount',
        action='store_true',
        default=False,
        help=
        'try to unmount left-overs of previous imount runs; may occasionally not be able to '
        'detect all mountpoints or detect too much mountpoints; use --casename to limit '
        'the unmount options')
    parser.add_argument('-w',
                        '--wait',
                        action='store_true',
                        default=False,
                        help='pause on some additional warnings')
    parser.add_argument('-k',
                        '--keep',
                        action='store_true',
                        default=False,
                        help='keep volumes mounted after program exits')
    parser.add_argument('--no-interaction',
                        action='store_true',
                        default=False,
                        help="do not ask for any user input, implies --keep")
    parser.add_argument(
        '-o',
        '--only-mount',
        default=None,
        help="specify which volume(s) you want to mount, comma-separated")
    parser.add_argument('-v',
                        '--verbose',
                        action='count',
                        default=False,
                        help='enable verbose output')
    parser.add_argument('-c',
                        '--color',
                        action='store_true',
                        default=False,
                        help='force colorizing the output')
    parser.add_argument('--no-color',
                        action='store_true',
                        default=False,
                        help='prevent colorizing the output')

    # Additional options
    parser.add_argument(
        '-r',
        '--reconstruct',
        action='store_true',
        default=False,
        help=
        'attempt to reconstruct the full filesystem tree; implies -s and mounts all partitions '
        'at once')
    parser.add_argument(
        '--carve',
        action='store_true',
        default=False,
        help=
        'automatically carve the free space of a mounted volume for deleted files'
    )
    parser.add_argument('--vshadow',
                        action='store_true',
                        default=False,
                        help='automatically mount volume shadow copies')

    # Specify options to the subsystem
    parser.add_argument('-md',
                        '--mountdir',
                        default=None,
                        help='specify other directory for volume mountpoints')
    parser.add_argument(
        '-p',
        '--pretty',
        action='store_true',
        default=False,
        help=
        'use pretty names for mount points; useful in combination with --mountdir'
    )
    parser.add_argument(
        '-cn',
        '--casename',
        default=None,
        help=
        'name to add to the --mountdir, often used in conjunction with --pretty'
    )
    parser.add_argument(
        '-rw',
        '--read-write',
        action='store_true',
        default=False,
        help=
        'mount image read-write by creating a local write-cache file in a temp directory; '
        'implies --disk-mounter=xmount')
    parser.add_argument(
        '-m',
        '--disk-mounter',
        choices=DISK_MOUNTERS,
        default='auto',
        help=
        'use other tool to mount the initial images; results may vary between methods and if '
        'something doesn\'t work, try another method; dummy can be used when base should not be '
        'mounted (default: auto)')
    parser.add_argument(
        '-d',
        '--volume-detector',
        choices=['pytsk3', 'mmls', 'parted', 'auto'],
        default='auto',
        help=
        'use other volume detection method; pytsk3 and mmls should provide identical results, '
        'though pytsk3 is using the direct C API of mmls, but requires pytsk3 to be installed; '
        'auto distinguishes between pytsk3 and mmls only '
        '(default: auto)')
    parser.add_argument(
        '--vstypes',
        action=AppendDictAction,
        default={'*': 'detect'},
        help=
        'specify type of volume system (partition table); if you don\'t know, '
        'use "detect" to try to detect (default: detect)')
    parser.add_argument(
        '--fstypes',
        action=AppendDictAction,
        default={'?': 'unknown'},
        help=
        "allows the specification of the file system type per volume number; format: 0.1=lvm,...; "
        "use volume number ? for all undetected file system types and * for all file systems; "
        "accepted file systems types are {}".format(
            ", ".join(FILE_SYSTEM_TYPES)) +
        ", and none only for the ? volume (defaults to unknown)")
    parser.add_argument(
        '--keys',
        action=AppendDictAction,
        default={},
        help=
        "allows the specification of key material per volume number; format: 0.1=p:pass,...; "
        "exact format depends on volume type")

    # Toggles for default settings you may perhaps want to override

    toggroup = parser.add_argument_group('toggles')
    toggroup.add_argument(
        '--single',
        action='store_true',
        default=False,
        help=
        "do not try to find a volume system, but assume the image contains a single volume"
    )
    toggroup.add_argument(
        '--no-single',
        action='store_true',
        default=False,
        help=
        "prevent trying to mount the image as a single volume if no volume system was found"
    )

    args = parser.parse_args()
    col = get_coloring_func(color=args.color, no_color=args.color)

    # Set logging level for internal Python
    handler = logging.StreamHandler()
    handler.setFormatter(ImageMounterFormatter(col, verbosity=args.verbose))
    logger = logging.getLogger("imagemounter")
    logger.setLevel({
        0: logging.CRITICAL,
        1: logging.WARNING,
        2: logging.INFO
    }.get(args.verbose, logging.DEBUG))
    logger.addHandler(handler)

    # Check some prerequisites
    if os.geteuid():  # Not run as root
        print(col('[!] Not running as root!', 'yellow'))

    if 'a' in __version__ or 'b' in __version__:
        print(
            col("Development release v{0}. Please report any bugs you encounter."
                .format(__version__),
                attrs=['dark']))
        print(
            col("Bug reports: use -vvvv to get maximum verbosity and include  imount --check  output in your report",
                attrs=['dark']))
        print(
            col("Critical bug? Use git tag to list all versions and use git checkout <version>",
                attrs=['dark']))

    # Make args.single default to None
    if args.single == args.no_single:
        args.single = None
    elif args.single:
        args.single = True
    elif args.no_single:
        args.single = False

    # If --no-interaction is specified, imply --keep and not --wait
    if args.no_interaction:
        args.keep = True
        if args.wait:
            print(
                col(
                    "[!] --no-interaction can't be used in conjunction with --wait",
                    'yellow'))
            args.wait = False

    # Check if mount method supports rw
    if args.disk_mounter not in ('xmount', 'auto') and args.read_write:
        print(
            col(
                "[!] {0} does not support mounting read-write! Will mount read-only."
                .format(args.disk_mounter), 'yellow'))
        args.read_write = False

    # Check if mount method is available
    mount_command = 'avfsd' if args.disk_mounter == 'avfs' else args.disk_mounter
    if args.disk_mounter not in (
            'auto', 'dummy') and not _util.command_exists(mount_command):
        print(col("[-] {0} is not installed!".format(args.disk_mounter),
                  'red'))
        sys.exit(1)
    elif args.disk_mounter == 'auto' and not any(
            map(_util.command_exists,
                ('xmount', 'affuse', 'ewfmount', 'vmware-mount', 'avfsd'))):
        print(
            col(
                "[-] No tools installed to mount the image base! Please install xmount, affuse (afflib-tools), "
                "ewfmount (ewf-tools), vmware-mount or avfs first.", 'red'))
        sys.exit(1)

    # Check if detection method is available
    if args.volume_detector == 'pytsk3' and not _util.module_exists('pytsk3'):
        print(col("[-] pytsk3 module does not exist!", 'red'))
        sys.exit(1)
    elif args.volume_detector in ('mmls',
                                  'parted') and not _util.command_exists(
                                      args.volume_detector):
        print(
            col("[-] {0} is not installed!".format(args.volume_detector),
                'red'))
        sys.exit(1)
    elif args.volume_detector == 'auto' and not any(
        (_util.module_exists('pytsk3'), _util.command_exists('mmls'),
         _util.command_exists('parted'))):
        print(
            col(
                "[-] No tools installed to detect volumes! Please install mmls (sleuthkit), pytsk3 or parted first.",
                'red'))
        sys.exit(1)

    if args.fstypes:
        for k, v in args.fstypes.items():
            if v.strip() not in FILE_SYSTEM_TYPES and v.strip() not in VOLUME_SYSTEM_TYPES \
                    and not (k == '?' and v.strip().lower() == 'none'):
                print(
                    "[!] Error while parsing --fstypes: {} is invalid".format(
                        v))
                sys.exit(1)

    if '*' in args.fstypes:
        print(
            "[!] You are forcing the file system type to {0}. This may cause unexpected results."
            .format(args.fstypes['*']))
    elif '?' in args.fstypes and args.fstypes['?'] not in ('unknown', 'none'):
        print(
            "[!] You are using the file system type {0} as fallback. This may cause unexpected results."
            .format(args.fstypes['?']))

    if args.only_mount:
        args.only_mount = args.only_mount.split(',')

    if args.vstypes:
        for k, v in args.vstypes.items():
            if v.strip() not in VOLUME_SYSTEM_TYPES:
                print(
                    "[!] Error while parsing --vstypes: {} is invalid".format(
                        v))
                sys.exit(1)

    if args.carve and not _util.command_exists('photorec'):
        print(
            col(
                "[-] The photorec command (part of testdisk package) is required to carve, but is not "
                "installed. Carving will be disabled.", 'yellow'))
        args.carve = False

    if args.vshadow and not _util.command_exists('vshadowmount'):
        print(
            col(
                "[-] The vhadowmount command is required to mount volume shadow copies, but is not "
                "installed. Mounting volume shadow copies will be disabled.",
                'yellow'))
        args.vshadow = False

    if (args.interactive or not args.images) and not args.unmount:
        from imagemounter.cli.shell import main
        main()
        return

    if args.unmount:
        unmounter = Unmounter(**vars(args))
        commands = unmounter.preview_unmount()
        if not commands:
            print("[+] Nothing to do")
            parser.exit()
        print(
            "[!] --unmount will rigorously clean anything that looks like a mount or volume group originating "
            "from this utility. You may regret using this if you have other mounts or volume groups that are "
            "similarly named. The following commands will be executed:")
        for c in commands:
            print("    {0}".format(c))
        try:
            input(">>> Press [enter] to continue or ^C to cancel... ")
            unmounter.unmount()
        except KeyboardInterrupt:
            print("\n[-] Aborted.")
        sys.exit(0)

    # Enumerate over all images in the CLI
    images = []
    for num, image in enumerate(args.images):
        # If is a directory, find a E01 file in the directory
        if os.path.isdir(image):
            for f in glob.glob(os.path.join(image, '*.[E0]01')):
                images.append(f)
                break
            else:
                print(
                    col(
                        "[-] {0} is a directory not containing a .001 or .E01 file, aborting!"
                        .format(image), "red"))
                break
            continue

        elif not os.path.exists(image):
            print(
                col("[-] Image {0} does not exist, aborting!".format(image),
                    "red"))
            break

        images.append(image)

    else:
        p = None
        try:
            p = ImageParser(images, **vars(args))
            num = 0

            # Mount all disks. We could use .init, but where's the fun in that?
            for disk in p.disks:
                num += 1
                print('[+] Mounting image {0} using {1}...'.format(
                    disk.paths[0], disk.disk_mounter))

                # Mount the base image using the preferred method
                try:
                    disk.mount()
                except ImageMounterError:
                    print(
                        col(
                            "[-] Failed mounting base image. Perhaps try another mount method than {0}?"
                            .format(disk.disk_mounter), "red"))
                    return

                if args.read_write:
                    print('[+] Created read-write cache at {0}'.format(
                        disk.rwpath))

                disk.volumes.preload_volume_data()
                print('[+] Mounted raw image [{num}/{total}]'.format(
                    num=num, total=len(args.images)))

            sys.stdout.write("[+] Mounting volume...\r")
            sys.stdout.flush()
            has_left_mounted = False

            for volume in p.init_volumes(args.single,
                                         args.only_mount,
                                         swallow_exceptions=True):
                try:
                    # something failed?
                    if not volume.mountpoint and not volume.loopback:
                        if volume.exception and volume.size is not None and volume.size <= 1048576:
                            print(
                                col(
                                    '[-] Exception while mounting small volume {0}'
                                    .format(volume.get_description()),
                                    'yellow'))
                            if args.wait:
                                input(
                                    col('>>> Press [enter] to continue... ',
                                        attrs=['dark']))

                        elif isinstance(volume.exception,
                                        UnsupportedFilesystemError
                                        ) and volume.fstype == 'swap':
                            print(
                                col(
                                    '[-] Exception while mounting swap volume {0}'
                                    .format(volume.get_description()),
                                    'yellow'))
                            if args.wait:
                                input(
                                    col('>>> Press [enter] to continue... ',
                                        attrs=['dark']))

                        elif volume.exception:
                            print(
                                col(
                                    '[-] Exception while mounting {0}'.format(
                                        volume.get_description()), 'red'))
                            if not args.no_interaction:
                                input(
                                    col('>>> Press [enter] to continue... ',
                                        attrs=['dark']))
                        elif volume.flag != 'alloc':
                            if args.wait or args.verbose:  # do not show skipped messages by default
                                print(
                                    col(
                                        '[-] Skipped {0} {1} volume'.format(
                                            volume.get_description(),
                                            volume.flag), 'yellow'))
                            if args.wait:
                                input(
                                    col('>>> Press [enter] to continue... ',
                                        attrs=['dark']))
                        elif not volume._should_mount(args.only_mount):
                            print(
                                col(
                                    '[-] Skipped {0}'.format(
                                        volume.get_description()), 'yellow'))
                        else:
                            print(
                                col(
                                    '[-] Could not mount volume {0}'.format(
                                        volume.get_description()), 'yellow'))
                            if args.wait:
                                input(
                                    col('>>> Press [enter] to continue... ',
                                        attrs=['dark']))

                        if args.carve and volume.flag in ('alloc', 'unalloc'):
                            sys.stdout.write("[+] Carving volume...\r")
                            sys.stdout.flush()
                            try:
                                path = volume.carve(freespace=False)
                            except ImageMounterError:
                                print(col('[-] Carving failed.', 'red'))
                            else:
                                print('[+] Carved data is available at {0}.'.
                                      format(col(path, 'green',
                                                 attrs=['bold'])))
                        else:
                            continue  # we do not need the unmounting sequence

                    else:
                        # it all was ok
                        if volume.mountpoint:
                            print('[+] Mounted volume {0} on {1}.'.format(
                                col(volume.get_description(), attrs=['bold']),
                                col(volume.mountpoint, 'green',
                                    attrs=['bold'])))
                        elif volume.loopback:  # fallback, generally indicates error.
                            print('[+] Mounted volume {0} as loopback on {1}.'.
                                  format(
                                      col(volume.get_description(),
                                          attrs=['bold']),
                                      col(volume.loopback,
                                          'green',
                                          attrs=['bold'])))
                            print(
                                col(
                                    '[-] Could not detect further volumes in the loopback device.',
                                    'red'))

                        if args.carve:
                            sys.stdout.write("[+] Carving volume...\r")
                            sys.stdout.flush()
                            try:
                                path = volume.carve()
                            except ImageMounterError:
                                print(col('[-] Carving failed.', 'red'))
                            else:
                                print('[+] Carved data is available at {0}.'.
                                      format(col(path, 'green',
                                                 attrs=['bold'])))

                        if args.vshadow and volume.fstype == 'ntfs':
                            sys.stdout.write(
                                "[+] Mounting volume shadow copies...\r")
                            sys.stdout.flush()
                            try:
                                volumes = volume.detect_volume_shadow_copies()
                            except ImageMounterError:
                                print(
                                    col(
                                        '[-] Volume shadow copies could not be mounted.',
                                        'red'))
                            else:
                                for v in volumes:
                                    try:
                                        v.init_volume()
                                    except ImageMounterError:
                                        print(
                                            col(
                                                '[-] Volume shadow copy {} not mounted'
                                                .format(v), 'red'))
                                    else:
                                        print(
                                            '[+] Volume shadow copy available at {0}.'
                                            .format(
                                                col(v.mountpoint,
                                                    'green',
                                                    attrs=['bold'])))

                    # Do not offer unmount when reconstructing
                    if args.reconstruct or args.keep:
                        has_left_mounted = True
                        continue

                    input(
                        col('>>> Press [enter] to unmount the volume, or ^C to keep mounted... ',
                            attrs=['dark']))

                    # Case where image should be unmounted, but has failed to do so. Keep asking whether the user wants
                    # to unmount.
                    while True:
                        try:
                            volume.unmount()
                            break
                        except ImageMounterError:
                            try:
                                print(
                                    col(
                                        "[-] Error unmounting volume. Perhaps files are still open?",
                                        "red"))
                                input(
                                    col('>>> Press [enter] to retry unmounting, or ^C to skip... ',
                                        attrs=['dark']))
                            except KeyboardInterrupt:
                                has_left_mounted = True
                                print("")
                                break
                except KeyboardInterrupt:
                    has_left_mounted = True
                    print("")
                sys.stdout.write("[+] Mounting volume...\r")
                sys.stdout.flush()

            for disk in p.disks:
                if [x for x in disk.volumes if x.was_mounted] == 0:
                    if disk.vstype != 'detect':
                        print(
                            col(
                                '[?] Could not determine volume information of {0}. Image may be empty, '
                                'or volume system type {0} was incorrect.'.
                                format(disk.vstype.upper()), 'yellow'))
                    elif args.single is False:
                        print(
                            col(
                                '[?] Could not determine volume information. Image may be empty, or volume system '
                                'type could not be detected. Try explicitly providing the volume system type with '
                                '--vstypes or mounting as a single volume with --single',
                                'yellow'))
                    else:
                        print(
                            col(
                                '[?] Could not determine volume information. Image may be empty, or volume system '
                                'type could not be detected. Try explicitly providing the volume system type with '
                                '--vstypes.', 'yellow'))
                    if args.wait:
                        input(
                            col('>>> Press [enter] to continue... ',
                                attrs=['dark']))

            print('[+] Parsed all volumes!')

            # Perform reconstruct if required
            if args.reconstruct:
                # Reverse order so '/' gets unmounted last

                print("[+] Performing reconstruct... ")
                try:
                    root = p.reconstruct()
                except NoRootFoundError:
                    print(
                        col(
                            "[-] Failed reconstructing filesystem: could not find root directory.",
                            'red'))
                else:
                    failed = []
                    for disk in p.disks:
                        failed.extend([
                            x for x in disk.volumes
                            if 'bindmounts' not in x._paths and x.mountpoint
                            and x != root
                        ])
                    if failed:
                        print(
                            "[+] Parts of the filesystem are reconstructed in {0}."
                            .format(
                                col(root.mountpoint, "green", attrs=["bold"])))
                        for m in failed:
                            print("    {0} was not reconstructed".format(
                                m.mountpoint))
                    else:
                        print(
                            "[+] The entire filesystem is reconstructed in {0}."
                            .format(
                                col(root.mountpoint, "green", attrs=["bold"])))
                if not args.keep:
                    input(
                        col(">>> Press [enter] to unmount all volumes... ",
                            attrs=['dark']))
            elif has_left_mounted and not args.keep:
                input(
                    col(">>> Some volumes were left mounted. Press [enter] to unmount all... ",
                        attrs=['dark']))

        except KeyboardInterrupt:
            print('\n[+] User pressed ^C, aborting...')
            return

        except Exception as e:
            import traceback
            traceback.print_exc()
            print(col("[-] {0}".format(e), 'red'))
            if not args.no_interaction:
                input(col(">>> Press [enter] to continue.", attrs=['dark']))

        finally:
            if args.keep:
                print("[+] Analysis complete.")
            elif not p:
                print("[-] Crashed before creating parser")
            else:
                print('[+] Analysis complete, unmounting...')

                # All done with this image, unmount it
                try:
                    remove_rw = p.rw_active() and 'y' in input(
                        '>>> Delete the rw cache file? [y/N] ').lower()
                except KeyboardInterrupt:
                    remove_rw = False

                while True:
                    try:
                        p.clean(remove_rw)
                        print("[+] All cleaned up")
                        break
                    except ImageMounterError:
                        try:
                            print(
                                col(
                                    "[-] Error unmounting base image. Perhaps volumes are still open?",
                                    'red'))
                            input(
                                col('>>> Press [enter] to retry unmounting, or ^C to cancel... ',
                                    attrs=['dark']))
                        except KeyboardInterrupt:
                            print("")  # ^C does not print \n
                            break
Beispiel #13
0
    def is_available(self):
        """Whether the command is available on the system.

        :rtype: bool
        """
        return _util.command_exists(self.name)
Beispiel #14
0
 def add_method_if_exists(method):
     if (method == 'avfs' and _util.command_exists('avfsd')) or \
             (method == 'nbd' and _util.command_exists('qemu-nbd')) or \
             _util.command_exists(method):
         methods.append(method)
Beispiel #15
0
def main():
    class MyParser(argparse.ArgumentParser):
        def error(self, message):
            sys.stderr.write('error: {0}\n'.format(message))
            self.print_help()
            sys.exit(2)

    parser = MyParser(description='Utility to mount volumes in Encase and dd images locally.')
    parser.add_argument('images', nargs='*',
                        help='path(s) to the image(s) that you want to mount; generally just the first file (e.g. '
                             'the .E01 or .001 file) or the folder containing the files is enough in the case of '
                             'split files')

    # Special options
    parser.add_argument('--version', action='version', version=__version__, help='display version and exit')
    parser.add_argument('--check', action=CheckAction, nargs=0,
                        help='do a system check and list which tools are installed')
    parser.add_argument('-i', '--interactive', action='store_true', default=False,
                        help='enter the interactive shell')

    # Utility specific
    parser.add_argument('-u', '--unmount', action='store_true', default=False,
                        help='try to unmount left-overs of previous imount runs; may occasionally not be able to '
                             'detect all mountpoints or detect too much mountpoints; use --casename to limit '
                             'the unmount options')
    parser.add_argument('-w', '--wait', action='store_true', default=False, help='pause on some additional warnings')
    parser.add_argument('-k', '--keep', action='store_true', default=False,
                        help='keep volumes mounted after program exits')
    parser.add_argument('--no-interaction', action='store_true', default=False,
                        help="do not ask for any user input, implies --keep")
    parser.add_argument('-o', '--only-mount', default=None,
                        help="specify which volume(s) you want to mount, comma-separated")
    parser.add_argument('--skip', default=None,
                        help="specify which volume(s) you do not want to mount, comma-separated")
    parser.add_argument('-v', '--verbose', action='count', default=False, help='enable verbose output')
    parser.add_argument('-c', '--color', action='store_true', default=False, help='force colorizing the output')
    parser.add_argument('--no-color', action='store_true', default=False, help='prevent colorizing the output')

    # Additional options
    parser.add_argument('-r', '--reconstruct', action='store_true', default=False,
                        help='attempt to reconstruct the full filesystem tree; implies -s and mounts all partitions '
                             'at once')
    parser.add_argument('--carve', action='store_true', default=False,
                        help='automatically carve the free space of a mounted volume for deleted files')
    parser.add_argument('--vshadow', action='store_true', default=False,
                        help='automatically mount volume shadow copies')

    # Specify options to the subsystem
    parser.add_argument('-md', '--mountdir', default=None,
                        help='specify other directory for volume mountpoints')
    parser.add_argument('-p', '--pretty', action='store_true', default=False,
                        help='use pretty names for mount points; useful in combination with --mountdir')
    parser.add_argument('-cn', '--casename', default=None,
                        help='name to add to the --mountdir, often used in conjunction with --pretty')
    parser.add_argument('-rw', '--read-write', action='store_true', default=False,
                        help='mount image read-write by creating a local write-cache file in a temp directory; '
                             'implies --disk-mounter=xmount')
    parser.add_argument('-m', '--disk-mounter', choices=DISK_MOUNTERS,
                        default='auto',
                        help='use other tool to mount the initial images; results may vary between methods and if '
                             'something doesn\'t work, try another method; dummy can be used when base should not be '
                             'mounted (default: auto)')
    parser.add_argument('-d', '--volume-detector', choices=['pytsk3', 'mmls', 'parted', 'auto'], default='auto',
                        help='use other volume detection method; pytsk3 and mmls should provide identical results, '
                             'though pytsk3 is using the direct C API of mmls, but requires pytsk3 to be installed; '
                             'auto distinguishes between pytsk3 and mmls only '
                             '(default: auto)')
    parser.add_argument('--vstypes', action=AppendDictAction, default={'*': 'detect'},
                        help='specify type of volume system (partition table); if you don\'t know, '
                             'use "detect" to try to detect (default: detect)')
    parser.add_argument('--fstypes', action=AppendDictAction, default={'?': 'unknown'},
                        help="allows the specification of the file system type per volume number; format: 0.1=lvm,...; "
                             "use volume number ? for all undetected file system types and * for all file systems; "
                             "accepted file systems types are {}".format(", ".join(FILE_SYSTEM_TYPES)) +
                             ", and none only for the ? volume (defaults to unknown)")
    parser.add_argument('--keys', action=AppendDictAction, default={},
                        help="allows the specification of key material per volume number; format: 0.1=p:pass,...; "
                             "exact format depends on volume type", allow_commas=False)
    parser.add_argument('--lazy-unmount', action='store_true', default=False,
                        help="enables lazily unmounting volumes and disks if direct unmounting fails")

    # Toggles for default settings you may perhaps want to override

    toggroup = parser.add_argument_group('toggles')
    toggroup.add_argument('--single', action='store_true', default=False,
                          help="do not try to find a volume system, but assume the image contains a single volume")
    toggroup.add_argument('--no-single', action='store_true', default=False,
                          help="prevent trying to mount the image as a single volume if no volume system was found")

    args = parser.parse_args()
    col = get_coloring_func(color=args.color, no_color=args.color)

    # Set logging level for internal Python
    handler = ImageMounterStreamHandler(col, args.verbose)
    logger = logging.getLogger("imagemounter")
    logger.setLevel({0: logging.CRITICAL, 1: logging.WARNING, 2: logging.INFO}.get(args.verbose, logging.DEBUG))
    logger.addHandler(handler)

    # Check some prerequisites
    if os.geteuid():  # Not run as root
        print(col('[!] Not running as root!', 'yellow'))

    if 'a' in __version__ or 'b' in __version__:
        print(col("Development release v{0}. Please report any bugs you encounter.".format(__version__),
                  attrs=['dark']))
        print(col("Bug reports: use -vvvv to get maximum verbosity and include  imount --check  output in your report",
                  attrs=['dark']))
        print(col("Critical bug? Use git tag to list all versions and use git checkout <version>", attrs=['dark']))

    # Make args.single default to None
    if args.single == args.no_single:
        args.single = None
    elif args.single:
        args.single = True
    elif args.no_single:
        args.single = False

    # If --no-interaction is specified, imply --keep and not --wait
    if args.no_interaction:
        args.keep = True
        if args.wait:
            print(col("[!] --no-interaction can't be used in conjunction with --wait", 'yellow'))
            args.wait = False

    # Check if mount method supports rw
    if args.disk_mounter not in ('xmount', 'auto') and args.read_write:
        print(col("[!] {0} does not support mounting read-write! Will mount read-only.".format(args.disk_mounter), 'yellow'))
        args.read_write = False

    # Check if mount method is available
    mount_command = 'avfsd' if args.disk_mounter == 'avfs' else args.disk_mounter
    if args.disk_mounter not in ('auto', 'dummy') and not _util.command_exists(mount_command):
        print(col("[-] {0} is not installed!".format(args.disk_mounter), 'red'))
        sys.exit(1)
    elif args.disk_mounter == 'auto' and not any(map(_util.command_exists, ('xmount', 'affuse', 'ewfmount', 'vmware-mount',
                                                                            'avfsd'))):
        print(col("[-] No tools installed to mount the image base! Please install xmount, affuse (afflib-tools), "
                  "ewfmount (ewf-tools), vmware-mount or avfs first.", 'red'))
        sys.exit(1)

    # Check if detection method is available
    if args.volume_detector == 'pytsk3' and not _util.module_exists('pytsk3'):
        print(col("[-] pytsk3 module does not exist!", 'red'))
        sys.exit(1)
    elif args.volume_detector in ('mmls', 'parted') and not _util.command_exists(args.volume_detector):
        print(col("[-] {0} is not installed!".format(args.volume_detector), 'red'))
        sys.exit(1)
    elif args.volume_detector == 'auto' and not any((_util.module_exists('pytsk3'), _util.command_exists('mmls'),
                                                     _util.command_exists('parted'))):
        print(col("[-] No tools installed to detect volumes! Please install mmls (sleuthkit), pytsk3 or parted first.",
                  'red'))
        sys.exit(1)

    if args.fstypes:
        for k, v in args.fstypes.items():
            if v.strip() not in FILE_SYSTEM_TYPES and v.strip() not in VOLUME_SYSTEM_TYPES \
                    and not (k == '?' and v.strip().lower() == 'none'):
                print("[!] Error while parsing --fstypes: {} is invalid".format(v))
                sys.exit(1)

    if '*' in args.fstypes:
        print("[!] You are forcing the file system type to {0}. This may cause unexpected results."
              .format(args.fstypes['*']))
    elif '?' in args.fstypes and args.fstypes['?'] not in ('unknown', 'none'):
        print("[!] You are using the file system type {0} as fallback. This may cause unexpected results."
              .format(args.fstypes['?']))

    if args.only_mount:
        args.only_mount = args.only_mount.split(',')
    if args.skip:
        args.skip = args.skip.split(',')

    if args.vstypes:
        for k, v in args.vstypes.items():
            if v.strip() not in VOLUME_SYSTEM_TYPES:
                print("[!] Error while parsing --vstypes: {} is invalid".format(v))
                sys.exit(1)

    if args.carve and not _util.command_exists('photorec'):
        print(col("[-] The photorec command (part of testdisk package) is required to carve, but is not "
                  "installed. Carving will be disabled.", 'yellow'))
        args.carve = False

    if args.vshadow and not _util.command_exists('vshadowmount'):
        print(col("[-] The vhadowmount command is required to mount volume shadow copies, but is not "
                  "installed. Mounting volume shadow copies will be disabled.", 'yellow'))
        args.vshadow = False

    if (args.interactive or not args.images) and not args.unmount:
        from imagemounter.cli.shell import main
        main()
        return

    if args.unmount:
        unmounter = Unmounter(**vars(args))
        commands = unmounter.preview_unmount()
        if not commands:
            print("[+] Nothing to do")
            parser.exit()
        print("[!] --unmount will rigorously clean anything that looks like a mount or volume group originating "
              "from this utility. You may regret using this if you have other mounts or volume groups that are "
              "similarly named. The following commands will be executed:")
        for c in commands:
            print("    {0}".format(c))
        try:
            input(">>> Press [enter] to continue or ^C to cancel... ")
            unmounter.unmount()
        except KeyboardInterrupt:
            print("\n[-] Aborted.")
        sys.exit(0)

    # Enumerate over all images in the CLI
    images = []
    for num, image in enumerate(args.images):
        # If is a directory, find a E01 file in the directory
        if os.path.isdir(image):
            for f in glob.glob(os.path.join(image, '*.[Ee0]01')):
                images.append(f)
                break
            else:
                print(col("[-] {0} is a directory not containing a .001 or .E01 file, aborting!".format(image), "red"))
                break
            continue

        elif not os.path.exists(image):
            print(col("[-] Image {0} does not exist, aborting!".format(image), "red"))
            break

        images.append(image)

    else:
        p = None
        try:
            p = ImageParser(images, **vars(args))
            num = 0

            # Mount all disks. We could use .init, but where's the fun in that?
            for disk in p.disks:
                num += 1
                print('[+] Mounting image {0} using {1}...'.format(disk.paths[0], disk.disk_mounter))

                # Mount the base image using the preferred method
                try:
                    disk.mount()
                except ImageMounterError:
                    print(col("[-] Failed mounting base image. Perhaps try another mount method than {0}?"
                              .format(disk.disk_mounter), "red"))
                    return

                if args.read_write:
                    print('[+] Created read-write cache at {0}'.format(disk.rwpath))

                disk.volumes.preload_volume_data()
                print('[+] Mounted raw image [{num}/{total}]'.format(num=num, total=len(args.images)))

            sys.stdout.write("[+] Mounting volume...\r")
            sys.stdout.flush()
            has_left_mounted = False

            for volume in p.init_volumes(args.single, args.only_mount, args.skip, swallow_exceptions=True):
                try:
                    # something failed?
                    if not volume.mountpoint and not volume.loopback:
                        if volume.exception and volume.size is not None and volume.size <= 1048576:
                            print(col('[-] Exception while mounting small volume {0}'.format(volume.get_description()),
                                      'yellow'))
                            if args.wait:
                                input(col('>>> Press [enter] to continue... ', attrs=['dark']))

                        elif isinstance(volume.exception, UnsupportedFilesystemError) and volume.fstype == 'swap':
                            print(col('[-] Exception while mounting swap volume {0}'.format(volume.get_description()),
                                      'yellow'))
                            if args.wait:
                                input(col('>>> Press [enter] to continue... ', attrs=['dark']))

                        elif volume.exception:
                            print(col('[-] Exception while mounting {0}'.format(volume.get_description()), 'red'))
                            if not args.no_interaction:
                                input(col('>>> Press [enter] to continue... ', attrs=['dark']))
                        elif volume.flag != 'alloc':
                            if args.wait or args.verbose:  # do not show skipped messages by default
                                print(col('[-] Skipped {0} {1} volume' .format(volume.get_description(), volume.flag),
                                          'yellow'))
                            if args.wait:
                                input(col('>>> Press [enter] to continue... ', attrs=['dark']))
                        elif not volume._should_mount(args.only_mount):
                            print(col('[-] Skipped {0}'.format(volume.get_description()), 'yellow'))
                        else:
                            print(col('[-] Could not mount volume {0}'.format(volume.get_description()), 'yellow'))
                            if args.wait:
                                input(col('>>> Press [enter] to continue... ', attrs=['dark']))

                        if args.carve and volume.flag in ('alloc', 'unalloc'):
                            sys.stdout.write("[+] Carving volume...\r")
                            sys.stdout.flush()
                            try:
                                path = volume.carve(freespace=False)
                            except ImageMounterError:
                                print(col('[-] Carving failed.', 'red'))
                            else:
                                print('[+] Carved data is available at {0}.'.format(col(path, 'green', attrs=['bold'])))
                        else:
                            continue  # we do not need the unmounting sequence

                    else:
                        # it all was ok
                        if volume.mountpoint:
                            print('[+] Mounted volume {0} on {1}.'.format(col(volume.get_description(), attrs=['bold']),
                                                                          col(volume.mountpoint, 'green',
                                                                              attrs=['bold'])))
                        elif volume.loopback:  # fallback, generally indicates error.
                            print('[+] Mounted volume {0} as loopback on {1}.'.format(col(volume.get_description(),
                                                                                          attrs=['bold']),
                                                                                      col(volume.loopback, 'green',
                                                                                          attrs=['bold'])))
                            print(col('[-] Could not detect further volumes in the loopback device.', 'red'))

                        if args.carve:
                            sys.stdout.write("[+] Carving volume...\r")
                            sys.stdout.flush()
                            try:
                                path = volume.carve()
                            except ImageMounterError:
                                print(col('[-] Carving failed.', 'red'))
                            else:
                                print('[+] Carved data is available at {0}.'.format(col(path, 'green', attrs=['bold'])))

                        if args.vshadow and volume.fstype == 'ntfs':
                            sys.stdout.write("[+] Mounting volume shadow copies...\r")
                            sys.stdout.flush()
                            try:
                                volumes = volume.detect_volume_shadow_copies()
                            except ImageMounterError:
                                print(col('[-] Volume shadow copies could not be mounted.', 'red'))
                            else:
                                for v in volumes:
                                    try:
                                        v.init_volume()
                                    except ImageMounterError:
                                        print(col('[-] Volume shadow copy {} not mounted'.format(v), 'red'))
                                    else:
                                        print('[+] Volume shadow copy available at {0}.'.format(col(v.mountpoint,
                                                                                                    'green',
                                                                                                    attrs=['bold'])))

                    # Do not offer unmount when reconstructing
                    if args.reconstruct or args.keep:
                        has_left_mounted = True
                        continue

                    input(col('>>> Press [enter] to unmount the volume, or ^C to keep mounted... ', attrs=['dark']))

                    # Case where image should be unmounted, but has failed to do so. Keep asking whether the user wants
                    # to unmount.
                    while True:
                        try:
                            volume.unmount(allow_lazy=args.lazy_unmount)
                            break
                        except ImageMounterError:
                            try:
                                print(col("[-] Error unmounting volume. Perhaps files are still open?", "red"))
                                input(col('>>> Press [enter] to retry unmounting, or ^C to skip... ', attrs=['dark']))
                            except KeyboardInterrupt:
                                has_left_mounted = True
                                print("")
                                break
                except KeyboardInterrupt:
                    has_left_mounted = True
                    print("")
                sys.stdout.write("[+] Mounting volume...\r")
                sys.stdout.flush()

            for disk in p.disks:
                if [x for x in disk.volumes if x.was_mounted] == 0:
                    if disk.vstype != 'detect':
                        print(col('[?] Could not determine volume information of {0}. Image may be empty, '
                                  'or volume system type {0} was incorrect.'.format(disk.vstype.upper()), 'yellow'))
                    elif args.single is False:
                        print(col('[?] Could not determine volume information. Image may be empty, or volume system '
                                  'type could not be detected. Try explicitly providing the volume system type with '
                                  '--vstypes or mounting as a single volume with --single', 'yellow'))
                    else:
                        print(col('[?] Could not determine volume information. Image may be empty, or volume system '
                                  'type could not be detected. Try explicitly providing the volume system type with '
                                  '--vstypes.', 'yellow'))
                    if args.wait:
                        input(col('>>> Press [enter] to continue... ', attrs=['dark']))

            print('[+] Parsed all volumes!')

            # Perform reconstruct if required
            if args.reconstruct:
                # Reverse order so '/' gets unmounted last

                print("[+] Performing reconstruct... ")
                try:
                    root = p.reconstruct()
                except NoRootFoundError:
                    print(col("[-] Failed reconstructing filesystem: could not find root directory.", 'red'))
                else:
                    failed = []
                    for disk in p.disks:
                        failed.extend([x for x in disk.volumes if 'bindmounts' not in x._paths and x.mountpoint and x != root])
                    if failed:
                        print("[+] Parts of the filesystem are reconstructed in {0}.".format(col(root.mountpoint,
                                                                                                 "green",
                                                                                                 attrs=["bold"])))
                        for m in failed:
                            print("    {0} was not reconstructed".format(m.mountpoint))
                    else:
                        print("[+] The entire filesystem is reconstructed in {0}.".format(col(root.mountpoint,
                                                                                              "green", attrs=["bold"])))
                if not args.keep:
                    input(col(">>> Press [enter] to unmount all volumes... ", attrs=['dark']))
            elif has_left_mounted and not args.keep:
                input(col(">>> Some volumes were left mounted. Press [enter] to unmount all... ", attrs=['dark']))

        except KeyboardInterrupt:
            print('\n[+] User pressed ^C, aborting...')
            return

        except Exception as e:
            import traceback
            traceback.print_exc()
            print(col("[-] {0}".format(e), 'red'))
            if not args.no_interaction:
                input(col(">>> Press [enter] to continue.", attrs=['dark']))

        finally:
            if args.keep:
                print("[+] Analysis complete.")
            elif not p:
                print("[-] Crashed before creating parser")
            else:
                print('[+] Analysis complete, unmounting...')

                # All done with this image, unmount it
                try:
                    remove_rw = p.rw_active() and 'y' in input('>>> Delete the rw cache file? [y/N] ').lower()
                except KeyboardInterrupt:
                    remove_rw = False

                while True:
                    try:
                        p.clean(remove_rw, allow_lazy=args.lazy_unmount)
                        print("[+] All cleaned up")
                        break
                    except ImageMounterError:
                        try:
                            print(col("[-] Error unmounting base image. Perhaps volumes are still open?", 'red'))
                            input(col('>>> Press [enter] to retry unmounting, or ^C to cancel... ', attrs=['dark']))
                        except KeyboardInterrupt:
                            print("")  # ^C does not print \n
                            break
Beispiel #16
0
    def is_available(self):
        """Whether the command is available on the system.

        :rtype: bool
        """
        return _util.command_exists(self.name)
Beispiel #17
0
    def __init__(self, parser, path, offset=0, vstype='detect', read_write=False, method='auto', detection='auto',
                 multifile=True, index=None, mount_directories=True, **args):
        """Instantiation of this class does not automatically mount, detect or analyse the disk. You will need the
        :func:`init` method for this.

        :param parser: the parent parser
        :type parser: :class:`ImageParser`
        :param int offset: offset of the disk where the volume (system) resides
        :param str vstype: the volume system type
        :param bool read_write: indicates whether the disk should be mounted with a read-write cache enabled
        :param str method: the method to mount the base image with
        :param str detection: the method to detect volumes in the volume system with
        :param bool multifile: indicates whether :func:`mount` should attempt to call the underlying mount method with
                all files of a split file when passing a single file does not work
        :param str index: the base index of this Disk
        :param bool mount_directories: indicates whether directories should also be 'mounted'
        :param args: arguments that should be passed down to :class:`Volume` objects
        """

        self.parser = parser

        # Find the type and the paths
        path = os.path.expandvars(os.path.expanduser(path))
        if _util.is_encase(path):
            self.type = 'encase'
        elif _util.is_vmware(path):
            self.type = 'vmdk'
        elif _util.is_compressed(path):
            self.type = 'compressed'
        else:
            self.type = 'dd'
        self.paths = sorted(_util.expand_path(path))

        self.offset = offset
        self.vstype = vstype.lower()

        self.block_size = BLOCK_SIZE

        self.read_write = read_write

        self.method = method

        if detection == 'auto':
            if _util.module_exists('pytsk3'):
                self.detection = 'pytsk3'
            elif _util.command_exists('mmls'):
                self.detection = 'mmls'
            else:
                self.detection = 'parted'
        else:
            self.detection = detection

        self.read_write = read_write
        self.rwpath = ""
        self.multifile = multifile
        self.index = index
        self.mount_directories = mount_directories
        self.args = args

        self.name = os.path.split(path)[1]
        self.mountpoint = ''
        self.avfs_mountpoint = ''
        self.volumes = []
        self.volume_source = ""

        self._disktype = defaultdict(dict)

        self.loopback = ""
        self.md_device = ""
Beispiel #18
0
def main():
    class MyParser(argparse.ArgumentParser):
        def error(self, message):
            sys.stderr.write('error: {0}\n'.format(message))
            self.print_help()
            sys.exit(2)

    class CheckAction(argparse.Action):
        def _check_command(self, command, package="", why=""):
            if _util.command_exists(command):
                print(" INSTALLED {}".format(command))
            elif why and package:
                print(" MISSING   {:<20}needed for {}, part of the {} package".format(command, why, package))
            elif why:
                print(" MISSING   {:<20}needed for {}".format(command, why))
            elif package:
                print(" MISSING   {:<20}part of the {} package".format(command, package))
            else:
                print(" MISSING   {}".format(command))

        def _check_module(self, module, pip_name="", why=""):
            if not pip_name:
                pip_name = module

            if module == "magic" and _util.module_exists(module):
                import magic
                if hasattr(magic, 'from_file'):
                    print(" INSTALLED {:<20}(Python package)".format(pip_name))
                elif hasattr(magic, 'open'):
                    print(" INSTALLED {:<20}(system package)".format(pip_name))
                else:
                    print(" ERROR     {:<20}expecting {}, found other module named magic".format(pip_name, pip_name))
            elif module != "magic" and _util.module_exists(module):
                print(" INSTALLED {}".format(pip_name))
            elif why:
                print(" MISSING   {:<20}needed for {}, install using pip".format(pip_name, why))
            else:
                print(" MISSING   {:<20}install using pip".format(pip_name, why))

        # noinspection PyShadowingNames
        def __call__(self, parser, namespace, values, option_string=None):
            print("The following commands are used by imagemounter internally. Without most commands, imagemounter "
                  "works perfectly fine, but may lack some detection or mounting capabilities.")
            print("-- Mounting base disk images (at least one required, first three recommended) --")
            self._check_command("xmount", "xmount", "several types of disk images")
            self._check_command("ewfmount", "ewf-tools", "EWF images (partially covered by xmount)")
            self._check_command("affuse", "afflib-tools", "AFF images (partially covered by xmount)")
            self._check_command("vmware-mount", why="VMWare disks")
            print("-- Detecting volumes and volume types (at least one required) --")
            self._check_command("mmls", "sleuthkit")
            self._check_module("pytsk3")
            self._check_command("parted", "parted")
            print("-- Detecting volume types (all recommended, first two highly recommended) --")
            self._check_command("fsstat", "sleuthkit")
            self._check_command("file", "libmagic1")
            self._check_module("magic", "python-magic")
            self._check_command("disktype", "disktype")
            print("-- Enhanced mounting and detecting disks (install when needed) --")
            self._check_command("mdadm", "mdadm", "RAID disks")
            self._check_command("cryptsetup", "cryptsetup", "LUKS containers")
            self._check_command("mountavfs", "avfs", "compressed disk images")
            print("-- Mounting volumes (install when needed) --")
            self._check_command("mount.xfs", "xfsprogs", "XFS volumes")
            self._check_command("mount.ntfs", "ntfs-3g", "NTFS volumes")
            self._check_command("lvm", "lvm2", "LVM volumes")
            self._check_command("vmfs-fuse", "vmfs-tools", "VMFS volumes")
            self._check_command("mount.jffs2", "mtd-tools", "JFFS2 volumes")
            self._check_command("mount.squashfs", "squashfs-tools", "SquashFS volumes")
            parser.exit()

    parser = MyParser(description='Utility to mount volumes in Encase and dd images locally.')
    parser.add_argument('images', nargs='*',
                        help='path(s) to the image(s) that you want to mount; generally just the first file (e.g. '
                             'the .E01 or .001 file) or the folder containing the files is enough in the case of '
                             'split files')

    # Special options
    parser.add_argument('--version', action='version', version=__version__, help='display version and exit')
    parser.add_argument('--check', action=CheckAction, nargs=0,
                        help='do a system check and list which tools are installed')

    # Utility specific
    parser.add_argument('-u', '--unmount', action='store_true', default=False,
                        help='try to unmount left-overs of previous imount runs; may occasionally not be able to '
                             'detect all mountpoints or detect too much mountpoints; use --casename to limit '
                             'the unmount options')
    parser.add_argument('-w', '--wait', action='store_true', default=False, help='pause on some additional warnings')
    parser.add_argument('-k', '--keep', action='store_true', default=False,
                        help='keep volumes mounted after program exits')
    parser.add_argument('--no-interaction', action='store_true', default=False,
                        help="do not ask for any user input, implies --keep")
    parser.add_argument('-v', '--verbose', action='count', default=False, help='enable verbose output')
    parser.add_argument('-c', '--color', action='store_true', default=False, help='force colorizing the output')
    parser.add_argument('--no-color', action='store_true', default=False, help='prevent colorizing the output')

    # Additional options
    parser.add_argument('-r', '--reconstruct', action='store_true', default=False,
                        help='attempt to reconstruct the full filesystem tree; implies -s and mounts all partitions '
                             'at once')
    parser.add_argument('--carve', action='store_true', default=False,
                        help='automatically carve the free space of a mounted volume for deleted files')

    # Specify options to the subsystem
    parser.add_argument('-md', '--mountdir', default=None,
                        help='specify other directory for volume mountpoints')
    parser.add_argument('-p', '--pretty', action='store_true', default=False,
                        help='use pretty names for mount points; useful in combination with --mountdir')
    parser.add_argument('-cn', '--casename', default=None,
                        help='name to add to the --mountdir, often used in conjunction with --pretty')
    parser.add_argument('-rw', '--read-write', action='store_true', default=False,
                        help='mount image read-write by creating a local write-cache file in a temp directory; '
                             'implies --method=xmount')
    parser.add_argument('-m', '--method', choices=['xmount', 'affuse', 'ewfmount', 'vmware-mount', 'avfs',
                                                   'auto', 'dummy'],
                        default='auto',
                        help='use other tool to mount the initial images; results may vary between methods and if '
                             'something doesn\'t work, try another method; dummy can be used when base should not be '
                             'mounted (default: auto)')
    parser.add_argument('-d', '--detection', choices=['pytsk3', 'mmls', 'parted', 'auto'], default='auto',
                        help='use other volume detection method; pytsk3 and mmls should provide identical results, '
                             'though pytsk3 is using the direct C API of mmls, but requires pytsk3 to be installed; '
                             'auto distinguishes between pytsk3 and mmls only '
                             '(default: auto)')
    parser.add_argument('--vstype', choices=VOLUME_SYSTEM_TYPES,
                        default="detect", help='specify type of volume system (partition table); if you don\'t know, '
                                               'use "detect" to try to detect (default: detect)')
    parser.add_argument('--fsfallback', choices=FILE_SYSTEM_TYPES + ('none', ), default='unknown',
                        help="specify fallback type of the filesystem, which is used when it could not be detected or "
                             "is unsupported; use unknown to mount without specifying type")
    parser.add_argument('--fsforce', action='store_true', default=False,
                        help="force the use of the filesystem type specified with --fsfallback for all volumes")
    parser.add_argument('--fstypes', default=None,
                        help="allows the specification of the filesystem type per volume number; format: 0.1=lvm, ...")

    # Toggles for default settings you may perhaps want to override
    parser.add_argument('--stats', action='store_true', default=False,
                        help='show limited information from fsstat, which will slow down mounting and may cause '
                             'random issues such as partitions being unreadable (default)')
    parser.add_argument('--no-stats', action='store_true', default=False,
                        help='do not show limited information from fsstat')
    parser.add_argument('--disktype', action='store_true', default=False,
                        help='use the disktype command to get even more information about the volumes (default)')
    parser.add_argument('--no-disktype', action='store_true', default=False,
                        help='do not use disktype to get more information')
    parser.add_argument('--raid', action='store_true', default=False,
                        help="try to detect whether the volume is part of a RAID array (default)")
    parser.add_argument('--no-raid', action='store_true', default=False,
                        help="prevent trying to mount the volume in a RAID array")
    parser.add_argument('--single', action='store_true', default=False,
                        help="do not try to find a volume system, but assume the image contains a single volume")
    parser.add_argument('--no-single', action='store_true', default=False,
                        help="prevent trying to mount the image as a single volume if no volume system was found")
    args = parser.parse_args()

    # Colorize the output by default if the terminal supports it
    if not args.color and args.no_color:
        args.color = False
    elif args.color:
        args.color = True
    else:
        args.color = _util.terminal_supports_color()

    if not args.color:
        # noinspection PyUnusedLocal,PyShadowingNames
        def col(s, *args, **kwargs):
            return s
    else:
        from termcolor import colored
        col = colored

    class ImageMounterFormatter(logging.Formatter):
        def format(self, record):
            msg = record.getMessage()
            if args.verbose >= 4 and record.exc_info:
                if not record.exc_text:
                    record.exc_text = self.formatException(record.exc_info)
                if msg[-1:] != "\n":
                    msg += "\n"
                msg += record.exc_text
            if record.levelno >= logging.WARNING:
                return col("[-] " + msg, 'cyan')
            elif record.levelno == logging.INFO:
                return col("[+] " + msg, 'cyan')
            elif msg.startswith('$'):
                return col("  " + msg, 'cyan')
            else:
                return col("    " + msg, 'cyan')

    # Set logging level for internal Python
    handler = logging.StreamHandler()
    handler.setFormatter(ImageMounterFormatter())
    logger = logging.getLogger("imagemounter")
    logger.setLevel({0: logging.CRITICAL, 1: logging.WARNING, 2: logging.INFO}.get(args.verbose, logging.DEBUG))
    logger.addHandler(handler)

    # Check some prerequisites
    if os.geteuid():  # Not run as root
        print(col('[!] Not running as root!', 'yellow'))

    if 'a' in __version__ or 'b' in __version__:
        print(col("Development release v{0}. Please report any bugs you encounter.".format(__version__),
                  attrs=['dark']))
        print(col("Critical bug? Use git tag to list all versions and use git checkout <version>", attrs=['dark']))

    # Always assume stats, except when --no-stats is present, and --stats is not.
    if not args.stats and args.no_stats:
        args.stats = False
    else:
        args.stats = True

    # Make args.disktype default to True
    explicit_disktype = False
    if not args.disktype and args.no_disktype:
        args.disktype = False
    else:
        if args.disktype:
            explicit_disktype = True
        args.disktype = True

    # Make args.raid default to True
    explicit_raid = False
    if not args.raid and args.no_raid:
        args.raid = False
    else:
        if args.raid:
            explicit_raid = True
        args.raid = True

    # Make args.single default to None
    if args.single == args.no_single:
        args.single = None
    elif args.single:
        args.single = True
    elif args.no_single:
        args.single = False

    # If --no-interaction is specified, imply --keep and not --wait
    if args.no_interaction:
        args.keep = True
        if args.wait:
            print(col("[!] --no-interaction can't be used in conjunction with --wait", 'yellow'))
            args.wait = False

    # Check if mount method supports rw
    if args.method not in ('xmount', 'auto') and args.read_write:
        print(col("[!] {0} does not support mounting read-write! Will mount read-only.".format(args.method), 'yellow'))
        args.read_write = False

    # Check if mount method is available
    mount_command = 'avfsd' if args.method == 'avfs' else args.method
    if args.method not in ('auto', 'dummy') and not _util.command_exists(mount_command):
        print(col("[-] {0} is not installed!".format(args.method), 'red'))
        sys.exit(1)
    elif args.method == 'auto' and not any(map(_util.command_exists, ('xmount', 'affuse', 'ewfmount', 'vmware-mount',
                                                                     'avfsd'))):
        print(col("[-] No tools installed to mount the image base! Please install xmount, affuse (afflib-tools), "
                  "ewfmount (ewf-tools), vmware-mount or avfs first.", 'red'))
        sys.exit(1)

    # Check if detection method is available
    if args.detection == 'pytsk3' and not _util.module_exists('pytsk3'):
        print(col("[-] pytsk3 module does not exist!", 'red'))
        sys.exit(1)
    elif args.detection in ('mmls', 'parted') and not _util.command_exists(args.detection):
        print(col("[-] {0} is not installed!".format(args.detection), 'red'))
        sys.exit(1)
    elif args.detection == 'auto' and not any((_util.module_exists('pytsk3'), _util.command_exists('mmls'),
                                               _util.command_exists('parted'))):
        print(col("[-] No tools installed to detect volumes! Please install mmls (sleuthkit), pytsk3 or parted first.",
                  'red'))
        sys.exit(1)

    # Check if raid is available
    if args.raid and not _util.command_exists('mdadm'):
        if explicit_raid:
            print(col("[!] RAID mount requires the mdadm command.", 'yellow'))
        args.raid = False

    if args.reconstruct and not args.stats:  # Reconstruct implies use of fsstat
        print("[!] You explicitly disabled stats, but --reconstruct implies the use of stats. Stats are re-enabled.")
        args.stats = True

    # Check if raid is available
    if args.disktype and not _util.command_exists('disktype'):
        if explicit_disktype:
            print(col("[-] The disktype command can not be used in this session, as it is not installed.", 'yellow'))
        args.disktype = False

    if args.stats and not _util.command_exists('fsstat'):
        print(col("[-] The fsstat command (part of sleuthkit package) is required to obtain stats, but is not "
                  "installed. Stats can not be obtained during this session.", 'yellow'))
        args.stats = False

        if args.reconstruct:
            print(col("[-] Reconstruction requires stats to be obtained, but stats can not be enabled.", 'red'))
            sys.exit(1)

    if args.fsforce:
        if not args.fsfallback or args.fsfallback == 'none':
            print("[-] You are forcing a file system type, but have not specified the type to use. Ignoring force.")
            args.fsforce = False
        else:
            print("[!] You are forcing the file system type to {0}. This may cause unexpected results."
                  .format(args.fsfallback))
    elif args.fsfallback and args.fsfallback not in ('unknown', 'none'):
        print("[!] You are using the file system type {0} as fallback. This may cause unexpected results."
              .format(args.fsfallback))

    if args.fstypes:
        try:
            fstypes = {}
            # noinspection PyUnresolvedReferences
            types = args.fstypes.split(',')
            for typ in types:
                idx, fstype = typ.split('=', 1)
                if fstype.strip() not in FILE_SYSTEM_TYPES:
                    print("[!] Error while parsing --fstypes: {} is invalid".format(fstype))
                else:
                    fstypes[idx.strip()] = fstype.strip()
            args.fstypes = fstypes
        except Exception as e:
            print("[!] Failed to parse --fstypes: {}".format(e))

    if args.vstype != 'detect' and args.single:
        print("[!] There's no point in using --single in combination with --vstype.")

    if args.carve and not _util.command_exists('photorec'):
        print(col("[-] The photorec command (part of testdisk package) is required to carve, but is not "
                  "installed. Carving will be disabled.", 'yellow'))
        args.carve = False

    if not args.images and not args.unmount:
        print(col("[-] You must specify at least one path to a disk image", 'red'))
        sys.exit(1)

    if args.unmount:
        unmounter = Unmounter(**vars(args))
        commands = unmounter.preview_unmount()
        if not commands:
            print("[+] Nothing to do")
            parser.exit()
        print("[!] --unmount will rigorously clean anything that looks like a mount or volume group originating "
              "from this utility. You may regret using this if you have other mounts or volume groups that are "
              "similarly named. The following commands will be executed:")
        for c in commands:
            print("    {0}".format(c))
        try:
            input(">>> Press [enter] to continue or ^C to cancel... ")
            unmounter.unmount()
        except KeyboardInterrupt:
            print("\n[-] Aborted.")
        sys.exit(0)

    # Enumerate over all images in the CLI
    images = []
    for num, image in enumerate(args.images):
        # If is a directory, find a E01 file in the directory
        if os.path.isdir(image):
            for f in glob.glob(os.path.join(image, '*.[E0]01')):
                images.append(f)
                break
            else:
                print(col("[-] {0} is a directory not containing a .001 or .E01 file, aborting!".format(image), "red"))
                break
            continue

        elif not os.path.exists(image):
            print(col("[-] Image {0} does not exist, aborting!".format(image), "red"))
            break

        images.append(image)

    else:
        p = None
        try:
            p = ImageParser(images, **vars(args))
            num = 0
            found_raid = False

            # Mount all disks. We could use .init, but where's the fun in that?
            for disk in p.disks:
                num += 1
                print('[+] Mounting image {0} using {1}...'.format(p.paths[0], disk.method))

                # Mount the base image using the preferred method
                if not disk.mount():
                    print(col("[-] Failed mounting base image. Perhaps try another mount method than {0}?"
                              .format(disk.method), "red"))
                    return

                if args.raid:
                    if disk.add_to_raid():
                        found_raid = True

                if args.read_write:
                    print('[+] Created read-write cache at {0}'.format(disk.rwpath))

                if args.disktype:
                    disk.load_disktype_data()
                print('[+] Mounted raw image [{num}/{total}]'.format(num=num, total=len(args.images)))

            sys.stdout.write("[+] Mounting volume...\r")
            sys.stdout.flush()
            has_left_mounted = False

            for volume in p.mount_volumes(args.single):
                try:
                    # something failed?
                    if not volume.mountpoint and not volume.loopback:
                        if volume.exception and volume.size is not None and volume.size <= 1048576:
                            print(col('[-] Exception while mounting small volume {0}'.format(volume.get_description()),
                                      'yellow'))
                            if args.wait:
                                input(col('>>> Press [enter] to continue... ', attrs=['dark']))

                        elif volume.exception:
                            print(col('[-] Exception while mounting {0}'.format(volume.get_description()), 'red'))
                            if not args.no_interaction:
                                input(col('>>> Press [enter] to continue... ', attrs=['dark']))
                        elif volume.flag != 'alloc':
                            if args.wait or args.verbose:  # do not show skipped messages by default
                                print(col('[-] Skipped {0} {1} volume' .format(volume.get_description(), volume.flag),
                                          'yellow'))
                            if args.wait:
                                input(col('>>> Press [enter] to continue... ', attrs=['dark']))
                        else:
                            print(col('[-] Could not mount volume {0}'.format(volume.get_description()), 'yellow'))
                            if args.wait:
                                input(col('>>> Press [enter] to continue... ', attrs=['dark']))

                        if args.carve and volume.flag in ('alloc', 'unalloc'):
                            sys.stdout.write("[+] Carving volume...\r")
                            sys.stdout.flush()
                            if volume.carve(freespace=False):
                                print('[+] Carved data is available at {0}.'.format(col(volume.carvepoint, 'green',
                                                                                        attrs=['bold'])))
                            else:
                                print(col('[-] Carving failed.', 'red'))
                        else:
                            continue  # we do not need the unmounting sequence

                    else:
                        # it all was ok
                        if volume.mountpoint:
                            print('[+] Mounted volume {0} on {1}.'.format(col(volume.get_description(), attrs=['bold']),
                                                                          col(volume.mountpoint, 'green',
                                                                              attrs=['bold'])))
                        elif volume.loopback:  # fallback, generally indicates error.
                            print('[+] Mounted volume {0} as loopback on {1}.'.format(col(volume.get_description(),
                                                                                          attrs=['bold']),
                                                                                      col(volume.loopback, 'green',
                                                                                          attrs=['bold'])))
                            print(col('[-] Could not detect further volumes in the loopback device.', 'red'))

                        if args.carve:
                            sys.stdout.write("[+] Carving volume...\r")
                            sys.stdout.flush()
                            if volume.carve():
                                print('[+] Carved data is available at {0}.'.format(col(volume.carvepoint, 'green',
                                                                                        attrs=['bold'])))
                            else:
                                print(col('[-] Carving failed.', 'red'))

                    # Do not offer unmount when reconstructing
                    if args.reconstruct or args.keep:
                        has_left_mounted = True
                        continue

                    input(col('>>> Press [enter] to unmount the volume, or ^C to keep mounted... ', attrs=['dark']))

                    # Case where image should be unmounted, but has failed to do so. Keep asking whether the user wants
                    # to unmount.
                    while True:
                        if volume.unmount():
                            break
                        else:
                            try:
                                print(col("[-] Error unmounting volume. Perhaps files are still open?", "red"))
                                input(col('>>> Press [enter] to retry unmounting, or ^C to skip... ', attrs=['dark']))
                            except KeyboardInterrupt:
                                has_left_mounted = True
                                print("")
                                break
                except KeyboardInterrupt:
                    has_left_mounted = True
                    print("")
                sys.stdout.write("[+] Mounting volume...\r")
                sys.stdout.flush()

            for disk in p.disks:
                if [x for x in disk.volumes if x.was_mounted] == 0:
                    if args.vstype != 'detect':
                        print(col('[?] Could not determine volume information of {0}. Image may be empty, '
                                  'or volume system type {0} was incorrect.'.format(args.vstype.upper()), 'yellow'))
                    elif found_raid:
                        print(col('[?] Could not determine volume information. Image may be empty, or volume system '
                                  'type could not be detected. Try explicitly providing the volume system type with '
                                  '--vstype, or providing more volumes to complete the RAID array.', 'yellow'))
                    elif not args.raid or args.single is False:
                        print(col('[?] Could not determine volume information. Image may be empty, or volume system '
                                  'type could not be detected. Try explicitly providing the volume system type with '
                                  '--vstype, mounting as RAID with --raid and/or mounting as a single volume with '
                                  '--single', 'yellow'))
                    else:
                        print(col('[?] Could not determine volume information. Image may be empty, or volume system '
                                  'type could not be detected. Try explicitly providing the volume system type with '
                                  '--vstype.', 'yellow'))
                    if args.wait:
                        input(col('>>> Press [enter] to continue... ', attrs=['dark']))

            print('[+] Parsed all volumes!')

            # Perform reconstruct if required
            if args.reconstruct:
                # Reverse order so '/' gets unmounted last

                print("[+] Performing reconstruct... ")
                root = p.reconstruct()
                if not root:
                    print(col("[-] Failed reconstructing filesystem: could not find root directory.", 'red'))
                else:
                    failed = []
                    for disk in p.disks:
                        failed.extend([x for x in disk.volumes if not x.bindmountpoint and x.mountpoint and x != root])
                    if failed:
                        print("[+] Parts of the filesystem are reconstructed in {0}.".format(col(root.mountpoint,
                                                                                                 "green",
                                                                                                 attrs=["bold"])))
                        for m in failed:
                            print("    {0} was not reconstructed".format(m.mountpoint))
                    else:
                        print("[+] The entire filesystem is reconstructed in {0}.".format(col(root.mountpoint,
                                                                                              "green", attrs=["bold"])))
                if not args.keep:
                    input(col(">>> Press [enter] to unmount all volumes... ", attrs=['dark']))
            elif has_left_mounted and not args.keep:
                input(col(">>> Some volumes were left mounted. Press [enter] to unmount all... ", attrs=['dark']))

        except KeyboardInterrupt:
            print('\n[+] User pressed ^C, aborting...')
            return

        except Exception as e:
            import traceback
            traceback.print_exc()
            print(col("[-] {0}".format(e), 'red'))
            if not args.no_interaction:
                input(col(">>> Press [enter] to continue.", attrs=['dark']))

        finally:
            if args.keep:
                print("[+] Analysis complete.")
            elif not p:
                print("[-] Crashed before creating parser")
            else:
                print('[+] Analysis complete, unmounting...')

                # All done with this image, unmount it
                try:
                    remove_rw = p.rw_active() and 'y' in input('>>> Delete the rw cache file? [y/N] ').lower()
                except KeyboardInterrupt:
                    remove_rw = False

                while True:
                    if p.clean(remove_rw):
                        break
                    else:
                        try:
                            print(col("[-] Error unmounting base image. Perhaps volumes are still open?", 'red'))
                            input(col('>>> Press [enter] to retry unmounting, or ^C to cancel... ', attrs=['dark']))
                        except KeyboardInterrupt:
                            print("")  # ^C does not print \n
                            break
                print("[+] All cleaned up")
Beispiel #19
0
    def _load_fsstat_data(self):
        """Using :command:`fsstat`, adds some additional information of the volume to the Volume."""

        if not _util.command_exists('fsstat'):
            logger.warning("fsstat is not installed, could not mount volume shadow copies")
            return

        process = None

        def stats_thread():
            try:
                cmd = ['fsstat', self.get_raw_path(), '-o', str(self.offset // self.disk.block_size)]
                logger.debug('$ {0}'.format(' '.join(cmd)))
                # noinspection PyShadowingNames
                process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

                for line in iter(process.stdout.readline, b''):
                    line = line.decode()
                    if line.startswith("File System Type:"):
                        self.info['statfstype'] = line[line.index(':') + 2:].strip()
                    elif line.startswith("Last Mount Point:") or line.startswith("Last mounted on:"):
                        self.info['lastmountpoint'] = line[line.index(':') + 2:].strip().replace("//", "/")
                    elif line.startswith("Volume Name:") and not self.info.get('label'):
                        self.info['label'] = line[line.index(':') + 2:].strip()
                    elif line.startswith("Version:"):
                        self.info['version'] = line[line.index(':') + 2:].strip()
                    elif line.startswith("Source OS:"):
                        self.info['version'] = line[line.index(':') + 2:].strip()
                    elif 'CYLINDER GROUP INFORMATION' in line:
                        # noinspection PyBroadException
                        try:
                            process.terminate()  # some attempt
                        except Exception:
                            pass
                        break

                if self.info.get('lastmountpoint') and self.info.get('label'):
                    self.info['label'] = "{0} ({1})".format(self.info['lastmountpoint'], self.info['label'])
                elif self.info.get('lastmountpoint') and not self.info.get('label'):
                    self.info['label'] = self.info['lastmountpoint']
                elif not self.info.get('lastmountpoint') and self.info.get('label') and \
                        self.info['label'].startswith("/"):  # e.g. /boot1
                    if self.info['label'].endswith("1"):
                        self.info['lastmountpoint'] = self.info['label'][:-1]
                    else:
                        self.info['lastmountpoint'] = self.info['label']

            except Exception as e:  # ignore any exceptions here.
                logger.exception("Error while obtaining stats.")
                pass

        thread = threading.Thread(target=stats_thread)
        thread.start()

        duration = 5  # longest possible duration for fsstat.
        thread.join(duration)
        if thread.is_alive():
            # noinspection PyBroadException
            try:
                process.terminate()
            except Exception:
                pass
            thread.join()
            logger.debug("Killed fsstat after {0}s".format(duration))
Beispiel #20
0
 def add_method_if_exists(method):
     if (method == "avfs" and _util.command_exists("avfsd")) or _util.command_exists(method):
         methods.append(method)
Beispiel #21
0
    def _load_fsstat_data(self):
        """Using :command:`fsstat`, adds some additional information of the volume to the Volume."""

        if not _util.command_exists('fsstat'):
            logger.warning("fsstat is not installed, could not mount volume shadow copies")
            return

        process = None

        def stats_thread():
            try:
                cmd = ['fsstat', self.get_raw_path(), '-o', str(self.offset // self.disk.block_size)]
                logger.debug('$ {0}'.format(' '.join(cmd)))
                # noinspection PyShadowingNames
                process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

                for line in iter(process.stdout.readline, b''):
                    line = line.decode()
                    if line.startswith("File System Type:"):
                        self.info['statfstype'] = line[line.index(':') + 2:].strip()
                    elif line.startswith("Last Mount Point:") or line.startswith("Last mounted on:"):
                        self.info['lastmountpoint'] = line[line.index(':') + 2:].strip().replace("//", "/")
                    elif line.startswith("Volume Name:") and not self.info.get('label'):
                        self.info['label'] = line[line.index(':') + 2:].strip()
                    elif line.startswith("Version:"):
                        self.info['version'] = line[line.index(':') + 2:].strip()
                    elif line.startswith("Source OS:"):
                        self.info['version'] = line[line.index(':') + 2:].strip()
                    elif 'CYLINDER GROUP INFORMATION' in line:
                        # noinspection PyBroadException
                        try:
                            process.terminate()  # some attempt
                        except Exception:
                            pass
                        break

                if self.info.get('lastmountpoint') and self.info.get('label'):
                    self.info['label'] = "{0} ({1})".format(self.info['lastmountpoint'], self.info['label'])
                elif self.info.get('lastmountpoint') and not self.info.get('label'):
                    self.info['label'] = self.info['lastmountpoint']
                elif not self.info.get('lastmountpoint') and self.info.get('label') and \
                        self.info['label'].startswith("/"):  # e.g. /boot1
                    if self.info['label'].endswith("1"):
                        self.info['lastmountpoint'] = self.info['label'][:-1]
                    else:
                        self.info['lastmountpoint'] = self.info['label']

            except Exception as e:  # ignore any exceptions here.
                logger.exception("Error while obtaining stats.")
                pass

        thread = threading.Thread(target=stats_thread)
        thread.start()

        duration = 5  # longest possible duration for fsstat.
        thread.join(duration)
        if thread.is_alive():
            # noinspection PyBroadException
            try:
                process.terminate()
            except Exception:
                pass
            thread.join()
            logger.debug("Killed fsstat after {0}s".format(duration))
Beispiel #22
0
 def add_method_if_exists(method):
     if (method == 'avfs' and _util.command_exists('avfsd')) or _util.command_exists(method):
         methods.append(method)
Beispiel #23
0
 def add_method_if_exists(method):
     if (method == 'avfs' and _util.command_exists('avfsd')
         ) or _util.command_exists(method):
         methods.append(method)