def run(self, path=""): """ Parse UsnJrnl files of a disk """ self.vss = self.myflag('vss') disk = getSourceImage(self.myconfig) self.usn_path = self.myconfig( 'voutdir') if self.vss else self.myconfig('outdir') check_folder(self.usn_path) self.usn_jrnl_file = os.path.join(self.usn_path, "UsnJrnl") self.filesystem = FileSystem(self.config, disk=disk) for p in disk.partitions: if not p.isMountable: continue if not self.vss: pname = ''.join(['p', p.partition]) self._parse_usnjrnl(pname) else: for v, dev in p.vss.items(): if dev == "": continue self._parse_usnjrnl(v) # Delete the temporal UsnJrnl dumped file if os.path.exists(self.usn_jrnl_file): os.remove(self.usn_jrnl_file) return []
def vss_mount(self): vshadowmount = self.myconfig('vshadowmount', '/usr/local/bin/vshadowmount') if len(self.vss) > 0: vp = os.path.join(self.mountaux, "vp%s" % self.partition) if len(self.fuse) == 0 or "/dev/fuse" not in self.fuse.keys(): check_folder(vp) if self.encrypted: run_command([ "sudo", vshadowmount, "-X", "allow_root", self.loop, vp ], logger=self.logger) else: run_command([ vshadowmount, "-X", "allow_root", self.imagefile, "-o", str(self.obytes), vp ], logger=self.logger) for p in self.vss.keys(): if self.vss[p] == "": mp = os.path.join(self.mountdir, p) self.mount_NTFS(imagefile=os.path.join( vp, "vss%s" % p[1:].split("p")[0]), mountpath=mp, offset=False) self.refreshMountedImages()
def mount_NTFS(self, imagefile=None, mountpath=None, offset=True): """ mount NTFS partition Confiugration section: :ntfs_args: arguments for mount. offset and sizelimit will be automatically appended to these arguments. This parameter will be managed as a format string. The current group id will be passed as an option `gid`. Args: imagefile (str): imagefile path (used for auxiliary mount point). If None, use self.imagefile. mountpath (str): mount the image on this path. If None, use `source/mnt/pXX`. offset (bool): Used to ignore disk offset (used for auxiliary mount point) """ args = self.myconfig('ntfs_args').format( gid=grp.getgrgid(os.getegid())[2]) if offset and self.obytes != 0: args = "%s,offset=%s,sizelimit=%s" % (args, self.obytes, self.size) mount = self.myconfig('mount', '/bin/mount') if not mountpath: mountpath = os.path.join(self.mountdir, "p%s" % self.partition) if not imagefile: imagefile = self.imagefile check_folder(mountpath) run_command( ["sudo", mount, imagefile, "-t", "ntfs-3g", "-o", args, mountpath], logger=self.logger)
def run(self, path=""): """ Parses lnk files, jumlists and customdestinations """ self.logger().info("Extraction of lnk files") self.Files = GetFiles(self.config, vss=self.myflag("vss")) self.filesystem = FileSystem(self.config) self.mountdir = self.myconfig('mountdir') lnk_path = self.myconfig('{}outdir'.format('v' if self.vss else '')) check_folder(lnk_path) users = get_user_list(self.mountdir, self.vss) artifacts = { 'lnk': { 'filename': "{}_lnk.csv", 'regex': r"{}/.*\.lnk$", 'function': self.lnk_parser }, 'autodest': { 'filename': "{}_jl.csv", 'regex': r"{}/.*\.automaticDestinations-ms$", 'function': self.automaticDest_parser }, 'customdest': { 'filename': "{}_jlcustom.csv", 'regex': r"{}/.*\.customDestinations-ms$", 'function': self.customDest_parser } } for user in users: usr = "******".format(user.split("/")[0], user.split("/")[2]) for a_name, artifact in artifacts.items(): out_file = os.path.join(lnk_path, artifact['filename'].format(usr)) files_list = list( self.Files.search(artifact['regex'].format(user))) self.logger().info( "Founded {} {} files for user {} at {}".format( len(files_list), a_name, user.split("/")[-1], user.split("/")[0])) if len(files_list) > 0: save_csv(artifact['function'](files_list), config=self.config, outfile=out_file, quoting=0, file_exists='OVERWRITE') self.logger().info( "{} extraction done for user {} at {}".format( a_name, user.split("/")[-1], user.split("/")[0])) self.logger().info("RecentFiles extraction done") return []
def mount_APFS(self): apfsmount = self.myconfig('apfsmount', '/usr/local/bin/apfs-fuse') mountpath = os.path.join(self.mountaux, "p%s" % self.partition) check_folder(mountpath) run_command([ "sudo", apfsmount, "-s", str(self.obytes), "-v", str(self.voln), self.imagefile, mountpath ], logger=self.logger) self.bindfs_mount()
def bindfs_mount(self): user = getpass.getuser() group = grp.getgrgid(os.getegid())[0] mountaux = os.path.join(self.mountaux, "p%s" % self.partition) check_folder(self.mountpath) bindfs = self.myconfig('bindfs', '/usr/bin/bindfs') run_command([ "sudo", bindfs, "-p", "550", "-u", user, "-g", group, mountaux, self.mountpath ], logger=self.logger)
def mount_fat(self, imagefile=None, mountpath=None, offset=True): args = self.myconfig('fat_args').format( gid=grp.getgrgid(os.getegid())[0]) if offset and self.obytes != 0: args = "%s,offset=%s,sizelimit=%s" % (args, self.obytes, self.size) mount = self.myconfig('mount', '/bin/mount') if not mountpath: mountpath = os.path.join(self.mountdir, "p%s" % self.partition) if not imagefile: imagefile = self.imagefile check_folder(mountpath) run_command(["sudo", mount, self.imagefile, "-o", args, mountpath], logger=self.logger)
def mount_ext(self): mount = self.myconfig('mount', '/bin/mount') mountpath = os.path.join(self.mountaux, "p%s" % self.partition) check_folder(mountpath) args = "%s,sizelimit=%s" % (self.myconfig('ext4_args'), self.size) if self.obytes != 0: args = "%s,offset=%s,sizelimit=%s" % (self.myconfig('ext4_args'), self.obytes, self.size) try: run_command(["sudo", mount, self.imagefile, "-o", args, mountpath], logger=self.logger) except Exception: args = args + ',norecovery' run_command(["sudo", mount, self.imagefile, "-o", args, mountpath], logger=self.logger) self.bindfs_mount()
def mount_bitlocker(self): if 'dislocker' in self.fuse.keys(): self.logger.info("Bitlocker partition p{} already mounted".format( self.partition)) return rec_key = self.myconfig('recovery_keys') dislocker = self.myconfig('dislocker', '/usr/bin/dislocker') mountauxpath = os.path.join(self.mountaux, "p%s" % self.partition) check_folder(mountauxpath) import time if rec_key == "": self.logger.warning( "Recovery key not available on partition p%s. Trying without key" % self.partition) try: cmd = "sudo {} -c -O {} -V {} -r {}".format( dislocker, self.obytes, self.imagefile, mountauxpath) run_command(cmd, logger=self.logger) time.sleep(4) self.refreshMountedImages() self.mount_NTFS(os.path.join(mountauxpath, "dislocker-file"), offset=False) except Exception: self.logger.error("Problems mounting partition p%s" % self.partition) return -1 else: self.logger.info("Trying to mount with recovery keys at {}".format( self.mountaux)) mountauxpath = os.path.join(self.mountaux, "p%s" % self.partition) for rk in rec_key.split( ','): # loop wih different recovery keys, comma separated try: cmd = "sudo {} -p{} -O {} -V {} -r {}".format( dislocker, rk, self.obytes, self.imagefile, mountauxpath) run_command(cmd, logger=self.logger) time.sleep(4) self.refreshMountedImages() self.mount_NTFS(os.path.join(mountauxpath, "dislocker-file"), offset=False) break except Exception: pass
def _getRawImagefile(self): # convert an Encase image to dd using ewfmount fuse_path = os.path.join(self.params('mountauxdir'), "encase") imagefile = os.path.join(fuse_path, "ewf1") self.auxdirectories.append(fuse_path) if not os.path.exists(imagefile): ewfmount = self.params('ewfmount', '/usr/bin/ewfmount') check_folder(fuse_path) try: run_command( [ewfmount, self.imagefile, "-X", "allow_root", fuse_path]) except Exception: self.logger.error("Cannot mount Encase imagefile=%s", self.imagefile) raise base.job.RVTError( "Cannot mount Encase imagefile={}".format(self.imagefile)) return imagefile
def mount_HFS(self, imagefile="", mountpath="", offset=True): # TODO: avoid infinite recursion if mount fails after having called fvdemount if mountpath == "": mountpath = os.path.join(self.mountaux, "p%s" % self.partition) if imagefile == "": imagefile = self.imagefile mount = self.myconfig('mount', '/bin/mount') check_folder(mountpath) args = "%s,sizelimit=%s" % (self.myconfig('hfs_args'), self.size) if offset and self.obytes != 0: args = "%s,offset=%s,sizelimit=%s" % (self.myconfig('hfs_args'), self.obytes, self.size) try: run_command(["sudo", mount, imagefile, "-o", args, mountpath], logger=self.logger) self.bindfs_mount() except Exception: self.fvde_mount()
def _getRawImagefile(self): fuse_path = os.path.join(self.params('mountauxdir'), "aff") imagefile = os.path.join(fuse_path, "%s.raw" % os.path.basename(self.imagefile)) self.auxdirectories.append(fuse_path) if not os.path.exists(imagefile): affuse = self.params('affuse', '/usr/bin/affuse') check_folder(fuse_path) try: run_command(["sudo", affuse, self.imagefile, fuse_path]) fuse_path = os.path.join(self.params('mountauxdir'), "aff") imagefile = os.path.join( fuse_path, "%s.raw" % os.path.basename(self.imagefile)) except Exception: self.logger.error("Cannot mount AFF imagefile=%s", self.imagefile) raise base.job.RVTError("Cannot mount AFF imagefile={}".format( self.imagefile)) return imagefile
def fvde_mount(self): self.logger.debug('Obtaining encrypted partition') fvdemount = self.myconfig('fvdemount', '/usr/local/bin/fvdemount') password = self.myconfig('password') mountpoint = os.path.join(self.mountaux, "vp%s" % self.partition) check_folder(mountpoint) # TODO: get 'EncryptedRoot.plist.wipekey' from recovery partition: https://github.com/libyal/libfvde/wiki/Mounting encryptedfile = os.path.join(self.myconfig('sourcedir'), 'EncryptedRoot.plist.wipekey') run_command([ 'sudo', fvdemount, "-e", encryptedfile, "-p", password, "-X", "allow_root", "-o", str(self.obytes), self.imagefile, mountpoint ], logger=self.logger) time.sleep(2) # let it do his work self.mount_HFS(imagefile=os.path.join(mountpoint, 'fvde1'), mountpath=os.path.join(self.mountaux, "p%s" % self.partition), offset=False)
def run(self, path=""): """ Creates a report based on the output of LnkExtract. """ vss = self.myflag('vss') self.logger().info("Generating lnk files report") self.mountdir = self.myconfig('mountdir') lnk_path = self.config.get('plugins.windows.RVT_lnk.LnkExtract', '{}outdir'.format('v' * vss)) report_lnk_path = self.myconfig('{}outdir'.format('v' * vss)) check_directory(lnk_path, error_missing=True) check_folder(report_lnk_path) outfile = os.path.join(report_lnk_path, 'recentfiles.csv') save_csv(self.report_recent(lnk_path), config=self.config, outfile=outfile, quoting=0) return []
def run(self, path=None): """ The path is ignored, and the source image is used. """ vss = self.myflag('vss') fls = self.myconfig('fls', 'fls') apfs_fls = self.myconfig('apfs_fls', 'fls') mactime = self.myconfig('mactime', 'mactime') disk = getSourceImage(self.myconfig) tl_path = self.myconfig('outdir') if vss: tl_path = self.myconfig('voutdir') check_folder(tl_path) if not vss: self.logger().info("Generating BODY file for %s", disk.disknumber) body = os.path.join(tl_path, "{}_BODY.csv".format(disk.disknumber)) # create the body file with open(body, "wb") as f: for p in disk.partitions: mountpath = base.utils.relative_path( p.mountpath, self.myconfig('casedir')) if not p.isMountable: continue if not disk.sectorsize: # unkwown sector size run_command([ fls, "-s", "0", "-m", mountpath, "-r", "-o", str(p.osects), "-i", "raw", disk.imagefile ], stdout=f, logger=self.logger()) elif p.filesystem == "NoName": # APFS filesystems are identified as NoName, according to our experience try: run_command([ apfs_fls, "-B", str(p.block_number), "-s", "0", "-m", mountpath, "-r", "-o", str(p.osects), "-b", str(disk.sectorsize), "-i", "raw", disk.imagefile ], stdout=f, logger=self.logger()) except Exception: # sometimes, APFS filesystems report a wrong offset. Try again with offset*8 run_command([ apfs_fls, "-B", str(p.block_number), "-s", "0", "-m", mountpath, "-r", "-o", str(p.osects * 8), "-b", str(disk.sectorsize), "-i", "raw", disk.imagefile ], stdout=f, logger=self.logger()) else: # we know the sector size if p.encrypted: run_command([ fls, "-s", "0", "-m", mountpath, "-r", "-b", str(disk.sectorsize), p.loop ], stdout=f, logger=self.logger()) else: run_command([ fls, "-s", "0", "-m", mountpath, "-r", "-o", str(p.osects), "-b", str(disk.sectorsize), disk.imagefile ], stdout=f, logger=self.logger()) # create the timeline using mactime self.logger().info("Creating timeline of {}".format( disk.disknumber)) hsum = os.path.join(tl_path, "%s_hour_sum.csv" % disk.disknumber) fcsv = os.path.join(tl_path, "%s_TL.csv" % disk.disknumber) with open(fcsv, "wb") as f: run_command([ mactime, "-b", body, "-m", "-y", "-d", "-i", "hour", hsum ], stdout=f, logger=self.logger()) run_command(['sed', '-i', '1,2d', hsum]) # Delete header because full path is included else: # generate body and timeline for each VSS in the disk for p in disk.partitions: for v, dev in p.vss.items(): if dev != "": self.logger().info( "Generating BODY file for {}".format(v)) body = os.path.join(tl_path, "{}_BODY.csv".format(v)) with open(body, "wb") as f: mountpath = base.utils.relative_path( p.mountpath, self.myconfig('casedir')) run_command([ fls, "-s", "0", "-m", "%s" % mountpath, "-r", dev ], stdout=f, logger=self.logger()) self.logger().info( "Creating timeline for {}".format(v)) hsum = os.path.join(tl_path, "%s_hour_sum.csv" % v) fcsv = os.path.join(tl_path, "%s_TL.csv" % v) with open(fcsv, "wb") as f: run_command([ mactime, "-b", body, "-m", "-y", "-d", "-i", "hour", hsum ], stdout=f, logger=self.logger()) run_command([ 'sed', '-i', '1,2d', hsum ]) # Delete header because full path is included self.logger().info("Timelines generation done!") return []