Beispiel #1
0
class CompressFileAnalyst(interface.Analyst):
    """
    File compression digital forensic analyst.

    Parameters:
    analyst.compress-file.filename -- the name of the file to compress.
    analyst.compress-file.output -- the name of the output file.
    """

    # File-based compression parameters.
    filename = interface.Parameter("analyst.compress-file.filename")
    output = interface.Parameter("analyst.compress-file.output")

    def analyse(self, execution, sample):
        """
        Compress a file.
        """
        target = self.output.format(execution=execution)
        logger.info("Compressing '%s' to '%s'", self.filename, target)
        compress(self.filename, target)

    def discard(self, execution):
        """
        Discard the compressed file.
        """
        target = self.output.format(execution=execution)
        try:
            os.unlink(target)
        except OSError:
            # The file does not exist, this error is expected and can be
            # safely ignored.
            pass
Beispiel #2
0
class CompressSlotAnalyst(interface.Analyst):
    """
    Slot-based file compression digital forensic analyst.

    Parameters:
    analyst.compress-slot.slot -- the name of the slot.
    analyst.compress-slot.output -- the name of the output file.
    """

    # Slot-based compression parameters.
    slot = interface.Parameter("analyst.compress-slot.slot")
    output = interface.Parameter("analyst.compress-slot.output")

    def analyse(self, execution, sample):
        """
        Compress the file referenced in a sample slot.
        """
        source = sample[self.slot]
        target = self.output.format(execution=execution)
        logger.info("Compressing '%s' to '%s'", source, target)
        compress(source, target)

    def discard(self, execution):
        """
        Discard the compressed file.
        """
        target = self.output.format(execution=execution)
        try:
            os.unlink(target)
        except OSError:
            # The file does not exist, this error is expected and can be
            # safely ignored.
            pass
Beispiel #3
0
class LogicalPageAnalyst(interface.Analyst):
    """
    Logical memory page digital forensic analyst.

    Parameters:
    analyst.logical-page.image -- the name of the memory image file.
    analyst.logical-page.volatility -- the location of the volatility script.
    analyst.logical-page.output -- the name of the output file.
    """

    # Memory image analysis parameters.
    image = interface.Parameter("analyst.logical-page.image")
    volatility = interface.Parameter("analyst.logical-page.volatility")

    # Output parameters.
    output = interface.Parameter("analyst.logical-page.output")

    def analyse(self, execution, sample):
        """
        Analyse pages in a memory image.
        """
        logger.info("Generating a hash of logical pages in '%s'", self.image)
        # Create a file to contain the memory page analysis results.
        output_filename = self.output.format(execution=execution)
        handle = bz2.BZ2File(output_filename, "wb")
        writer = csv.DictWriter(handle, PAGE_HEADERS)
        writer.writeheader()
        # Invoke the memory image analysis process.
        invocation = [self.volatility, "-f", sample[self.image], PAGE_COMMAND]
        process = subprocess.Popen(invocation, stdout=subprocess.PIPE)
        # Extract the information describing each page in the memory image.
        pattern = re.compile(PAGE_PATTERN)
        for line in process.stdout:
            match = pattern.match(line)
            if not match is None:
                writer.writerow(match.groupdict())
        # Close the output handle and wait for the analysis process.
        logger.info("Finished logical memory page analysis")
        handle.close()
        process.wait()

    def discard(self, execution):
        """
        Discard the memory image logical page hash database.
        """
        output_filename = self.output.format(execution=execution)
        try:
            os.unlink(output_filename)
        except OSError:
            # The file does not exist, this error is expected and can be
            # safely ignored.
            pass
Beispiel #4
0
class InitialStateTechnique(interface.Technique):
    """
    Sample the initial state of a virtual machine.

    Parameters:
    technique.domain.host -- the address of the virtualisation platform.
    technique.domain.name -- the name of the virtual machine domain.
    technique.domain.snapshot -- the name of the virtual machine snapshot.

    Slots:
    memory.image -- the virtual machine memory image.
    """

    # Virtual machine domain parameters.
    domain_host = interface.Parameter("technique.domain.host")
    domain_name = interface.Parameter("technique.domain.name")
    domain_snapshot = interface.Parameter("technique.domain.snapshot")

    # Memory image slots.
    image = interface.Slot("memory.image")

    def allocate(self):
        """
        Allocate temporary resources.
        """
        self.image = tempfile.mktemp(".memory")

    def sample(self):
        """
        Sample the initial state of a virtual machine.
        """
        # Instantiate and revert the virtual machine to a snapshot.
        logger.info("Launching domain and reverting to snapshot")
        domain = emulator.Emulator(self.domain_host, self.domain_name)
        domain.revert(self.domain_snapshot)
        # Save an image of virtual machine memory.
        logger.info("Saving image of domain memory")
        domain.save_memory(self.image)
        # Terminate the virtual machine.
        logger.info("Terminating domain")
        domain.close()

    def release(self):
        """
        Release temporary resources.
        """
        logger.info("Unlinking memory image '%s'", self.image)
        os.unlink(self.image)
Beispiel #5
0
class MonitorInterventionBehaviour(interface.Behaviour):
    """
    Monitoring live digital forensic intervention behaviour.

    Parameters:
    technique.invocation.command -- the command-line acquisition invocation.
    technique.invocation.interval -- the invocation sampling interval.
    technique.invocation.cwd -- the working-directory of the invocation.

    Slots:
    acquisition.samples -- the performance samples of the invocation.
    acquisition.standard-output -- the standard output of the invocation.
    acquisition.standard-error -- the standard error of the invocation.
    acquisition.return-code -- the return code of the information.
    """

    # Memory image acquisition invocation parameters.
    invocation_command = interface.Parameter("technique.invocation.command")
    invocation_interval = interface.Parameter("technique.invocation.interval")
    invocation_cwd = interface.Parameter("technique.invocation.cwd")

    # Acquisition invocation outputs.
    samples = interface.Slot("acquisition.samples")
    standard_output = interface.Slot("acquisition.standard-output")
    standard_error = interface.Slot("acquisition.standard-error")
    return_code = interface.Slot("acquisition.return-code")

    def acquire(self, domain, agent):
        """
        Acquire live digital forensic evidence by intervening in the
        execution of the domain and sampling the performance of the
        acquisition technique.

        Arguments:
        domain -- the virtual machine domain.
        agent -- the virtual machine guest agent.
        """
        reply = agent.monitor(self.invocation_command,
                              self.invocation_interval, self.invocation_cwd)
        self.samples = reply["samples"]
        self.standard_output = reply["stdout"]
        self.standard_error = reply["stderr"]
        self.return_code = reply["return_code"]
Beispiel #6
0
class PhysicalPageAnalyst(interface.Analyst):
    """
    Physical memory page digital forensic analyst.

    Parameters:
    analyst.physical-page.image -- the name of the memory image file.
    analyst.physical-page.page-size -- the page size of the memory image.
    analyst.physical-page.output -- the name of the output file.
    """

    # Memory image analysis parameters.
    image = interface.Parameter("analyst.physical-page.image")
    page_size = interface.Parameter("analyst.physical-page.page-size")

    # Output parameters.
    output = interface.Parameter("analyst.physical-page.output")

    def analyse(self, execution, sample):
        """
        Analyse a memory image.
        """
        import pypette.analyst.storage as storage
        logger.info("Generating a hash of physical pages in '%s'", self.image)
        output_filename = self.output.format(execution=execution)
        handle = bz2.BZ2File(output_filename, "w")
        for block in storage.blocks(sample[self.image], self.page_size):
            checksum = hashlib.md5(block)
            handle.write(checksum.hexdigest())
            handle.write("\n")
        logger.info("Finished physical memory page analysis")
        handle.close()

    def discard(self, execution):
        """
        Discard the memory image physical page hash database.
        """
        output_filename = self.output.format(execution=execution)
        try:
            os.unlink(output_filename)
        except OSError:
            # The file does not exist, this error is expected and can be
            # safely ignored.
            pass
Beispiel #7
0
class ExperimentMetadataAnalyst(interface.Analyst):
    """
    Experiment metadata digital forensic analyst.
    """

    # Metadata analysis parameters.
    slots = interface.Parameter("analyst.experiment-metadata.slots")
    output = interface.Parameter("analyst.experiment-metadata.output")

    def __init__(self, parameters):
        """
        Initialise a new experiment metadata analyst.
        """
        super(ExperimentMetadataAnalyst, self).__init__(parameters)
        self.table = {}

    def analyse(self, execution, sample):
        """
        Collect metadata from a sample.
        """
        self.table[execution] = sample

    def collate(self):
        """
        Collate metadata from an experiment.
        """
        logger.info("Collating metadata for experiment")
        document = yaml.dump(self.table)
        with open(self.output, "w") as handle:
            handle.write(document)

    def discard(self, execution):
        """
        Discard the collected metadata.
        """
        try:
            del self.table[execution]
        except KeyError:
            # The record does not exist, this error is expected and can be
            # safely ignored.
            pass
Beispiel #8
0
class RandomDelayBehaviour(interface.Behaviour):
    """
    Random live digital forensic experiment delay behaviour.

    Parameters:
    technique.duration.mean -- the mean duration to idle the virtual machine.
    technique.duration.stdev -- the standard deviation of the duration.
    """

    # Delay parameters.
    mean = interface.Parameter("technique.duration.mean")
    stdev = interface.Parameter("technique.duration.stdev")

    def idle(self):
        """
        Allow the live digital forensic situation to execute without
        interaction for a period drawn from a normal distribution.
        """
        duration = random.normalvariate(self.mean, self.stdev)
        logger.info("Idling virtual machine for %d seconds", duration)
        time.sleep(duration)
Beispiel #9
0
class OpticalAcquisitionToolkit(interface.Behaviour):
    """
    Optical live digital forensic toolkit behaviour.

    Parameters:
    technique.toolkit.image -- the path of the toolkit image.
    technique.toolkit.device -- the device on which to attach the toolkit.
    technique.toolkit.tag -- the identification tag of the toolkit.
    """

    # Optical acquisition toolkit parameters.
    toolkit_image = interface.Parameter("technique.toolkit.image")
    toolkit_device = interface.Parameter("technique.toolkit.device")
    toolkit_tag = interface.Parameter("technique.toolkit.tag")

    def attach_toolkit(self, domain, agent):
        """
        Attach the optical live digital forensic acquisition toolkit to
        the domain.

        Arguments:
        domain -- the virtual machine domain.
        agent -- the virtual machine guest agent.
        """
        logger.info("Attaching optical acquisition toolkit to domain")
        domain.change_optical(self.toolkit_device, self.toolkit_image)
        agent.find(self.toolkit_tag)

    def detach_toolkit(self, domain, agent):
        """
        Detach the optical live digital forensic acquisition toolkit
        from the domain.

        Arguments:
        domain -- the virtual machine domain.
        agent -- the virtual machine guest agent.
        """
        logger.info("Detaching optical acquisition toolkit from domain")
        domain.eject_optical(self.toolkit_device)
Beispiel #10
0
class SampleMetadataAnalyst(interface.Analyst):
    """
    Sample metadata digital forensic analyst.
    
    Parameters:
    analyst.sample-metadata.slots -- the list of sample slots to record.
    analyst.sample-metadata.output -- the name of the output file.
    """

    # Metadata analysis parameters.
    slots = interface.Parameter("analyst.sample-metadata.slots")
    output = interface.Parameter("analyst.sample-metadata.output")

    def analyse(self, execution, sample):
        """
        Collect metadata from a sample.
        """
        logger.info("Collecting metadata for execution '%s'", execution)
        # Encode the sample metadata in a document.
        document = yaml.dump({slot: sample[slot] for slot in self.slots})
        # Save the metadata document to a file.
        output_filename = self.output.format(execution=execution)
        with open(output_filename, "w") as handle:
            handle.write(document)

    def discard(self, execution):
        """
        Discard the collected metadata.
        """
        output_filename = self.output.format(execution=execution)
        try:
            os.unlink(output_filename)
        except OSError:
            # The file does not exist, this error is expected and can be
            # safely ignored.
            pass
Beispiel #11
0
class FixedDelayBehaviour(interface.Behaviour):
    """
    Fixed live digital forensic experiment delay behaviour.

    Parameters:
    technique.duration -- the number of seconds to idle the virtual machine.
    """

    # Delay parameters.
    duration = interface.Parameter("technique.duration")

    def idle(self):
        """
        Allow the live digital forensic situation to execute without
        interaction for a fixed period.
        """
        logger.info("Idling virtual machine for %d seconds", self.duration)
        time.sleep(self.duration)
Beispiel #12
0
class MemoryFeatureAnalyst(interface.Analyst):
    """
    Memory image semantic feature analyst.

    Parameters:
    analyst.memory-feature.image -- the name of the memory image.
    analyst.memory-feature.profile -- the name of the memory image profile.
    analyst.memory-feature.modules -- the list of memory analysis modules.
    analyst.memory-feature.volatility -- the location of the volatility script.
    analyst.memory-feature.output -- the name of the output path.
    """

    # Memory image analysis parameters.
    image = interface.Parameter("analyst.memory-feature.image")
    profile = interface.Parameter("analyst.memory-feature.profile")
    modules = interface.Parameter("analyst.memory-feature.modules")
    volatility = interface.Parameter("analyst.memory-feature.volatility")

    # Output parameters.
    output = interface.Parameter("analyst.memory-feature.output")

    def analyse(self, execution, sample):
        """
        Analyse semantic features in a memory image.
        """
        path = self.output.format(execution=execution)
        os.mkdir(path)
        logger.info("Analysing semantic features from '%s'", self.image)
        for module in self.modules:
            command, headers, pattern = definitions[module]
            table = self.extract(sample[self.image], command, pattern)
            target = os.path.join(path, "{}.csv.bz2".format(module))
            self.write(target, headers, table)
        logger.info("Finished analysing semantic features")

    def extract(self, source, command, pattern):
        """
        Extract a table of semantic features from a memory image.

        Arguments:
        source -- the name of the memory image.
        command -- the name of the memory image analysis command.
        pattern -- the analysis command output pattern.
        """
        invocation = [
            self.volatility, "--profile", self.profile, "-f", source, command
        ]
        matcher = re.compile(pattern)
        try:
            data = subprocess.check_output(invocation)
            return [m.groupdict() for m in matcher.finditer(data)]
        except subprocess.CalledProcessError:
            logger.exception("Invocation of memory analysis command failed")
            raise

    def write(self, target, headers, table):
        """
        Write a table of extracted semantic features to a file.

        Arguments:
        target -- the name of the target file.
        headers -- the headers of the semantic feature table.
        table -- the semantic feature table.
        """
        handle = bz2.BZ2File(target, "wb")
        writer = csv.DictWriter(handle, headers)
        writer.writeheader()
        writer.writerows(table)
        handle.close()

    def discard(self, execution):
        """
        Discard the tables of semantic features.
        """
        path = self.output.format(execution=execution)
        try:
            shutil.rmtree(path)
        except OSError:
            # The file does not exist, this error is expected and can be
            # safely ignored.
            pass
Beispiel #13
0
class AcquisitionTechnique(interface.Technique):
    """
    Abstract live digital forensic memory acquisition technique.

    Parameters:
    technique.domain.host -- the address of the virtualisation platform.
    technique.domain.name -- the name of the virtual machine domain.
    technique.domain.snapshot -- the name of the virtual machine snapshot.
    technique.domain.agent -- the address of the virtual machine guest agent.

    Slots:
    memory.final -- the final virtual machine state memory image.
    agent.initial -- the initial guest agent performance sample.
    agent.final -- the final guest agent performance sample.
    time.sample.start -- the time at which sampling started.
    time.sample.finish -- the time at which sampling finished.
    time.acquisition.start -- the time at which acquisition started.
    time.acquisition.finish -- the time at which acquisition finished.
    """

    # Virtual machine domain parameters.
    domain_host = interface.Parameter("technique.domain.host")
    domain_name = interface.Parameter("technique.domain.name")
    domain_snapshot = interface.Parameter("technique.domain.snapshot")
    domain_agent = interface.Parameter("technique.domain.agent")

    # Memory image slots.
    image_final = interface.Slot("memory.final")

    # Guest agent performance slots.
    agent_initial = interface.Slot("agent.initial")
    agent_final = interface.Slot("agent.final")

    # Timing information slots.
    sample_start = interface.Slot("time.sample.start")
    sample_finish = interface.Slot("time.sample.finish")
    acquisition_start = interface.Slot("time.acquisition.start")
    acquisition_finish = interface.Slot("time.acquisition.finish")

    def allocate(self):
        """
        Allocate temporary resources.
        """
        logger.info("Allocating resources")
        self.image_final = tempfile.mktemp(".memory")
        self.allocate_behaviour()

    def sample(self):
        """
        Sample the effects of a live digital forensic memory acquisition
        intervention on a virtual machine.
        """
        self.sample_start = time.time()
        logger.info("Launching domain and reverting to snapshot")
        domain = emulator.Emulator(self.domain_host, self.domain_name)
        try:
            domain.revert(self.domain_snapshot)
            logger.info("Connecting to the virtual machine guest agent")
            agent = pypette.agent.Agent(self.domain_agent)
            self.acquisition_start = time.time()
            logger.info("Attaching acquisition toolkit and vector")
            domain.resume()
            self.agent_initial = agent.status()
            self.attach_toolkit(domain, agent)
            self.attach_vector(domain, agent)
            logger.info("Launching the live digital forensic acquisition")
            self.acquire(domain, agent)
            logger.info("Detaching acquisition toolkit and vector")
            self.detach_toolkit(domain, agent)
            self.detach_vector(domain, agent)
            self.acquisition_finish = time.time()
            self.agent_final = agent.status()
            logger.info("Collecting final memory image and terminating domain")
            domain.suspend()
            domain.save_memory(self.image_final)
        finally:
            domain.close()
        logger.info("Transferring the acquired memory image to the host")
        self.transfer_acquired_image()
        self.sample_finish = time.time()

    def attach_toolkit(self, domain, agent):
        """
        Attach the live digital forensic acquisition toolkit to the
        domain.

        Arguments:
        domain -- the virtual machine domain.
        agent -- the virtual machine guest agent.
        """
        raise NotImplementedError()

    def detach_toolkit(self, domain, agent):
        """
        Detach the live digital forensic acquisition toolkit from the
        domain.

        Arguments:
        domain -- the virtual machine domain.
        agent -- the virtual machine guest agent.
        """
        raise NotImplementedError()

    def attach_vector(self, domain, agent):
        """
        Attach the live digital forensic acquisition vector to the
        domain.

        Arguments:
        domain -- the virtual machine domain.
        agent -- the virtual machine guest agent.
        """
        raise NotImplementedError()

    def acquire(self, domain, agent):
        """
        Acquire live digital forensic evidence by intervening in the
        execution of the domain.

        Arguments:
        domain -- the virtual machine domain.
        agent -- the virtual machine guest agent.
        """
        pass

    def detach_vector(self, domain, agent):
        """
        Detach the live digital forensic acquisition vector from the
        domain.

        Arguments:
        domain -- the virtual machine domain.
        agent -- the virtual machine guest agent.
        """
        raise NotImplementedError()

    def transfer_acquired_image(self):
        """
        Transfer the acquired memory image from the acquisition vector
        to the host machine.
        """
        raise NotImplementedError()

    def release(self):
        """
        Release temporary resources.
        """
        logger.info("Releasing resources")
        if os.path.exists(self.image_final):
            os.unlink(self.image_final)
        self.release_behaviour()
Beispiel #14
0
class ControlTechnique(interface.Technique):
    """
    Live digital forensic control experiment technique.

    Parameters:
    technique.domain.host -- the address of the virtualisation platform.
    technique.domain.name -- the name of the virtual machine domain.
    technique.domain.snapshot -- the name of the virtual machine snapshot.

    Slots:
    memory.image -- the virtual machine memory image.
    """

    # Virtual machine domain parameters.
    domain_host = interface.Parameter("technique.domain.host")
    domain_name = interface.Parameter("technique.domain.name")
    domain_snapshot = interface.Parameter("technique.domain.snapshot")

    # Memory image slots.
    image = interface.Slot("memory.image")

    def allocate(self):
        """
        Allocate temporary resources.
        """
        logger.info("Allocating file for memory image")
        self.image = tempfile.mktemp(".memory")

    def sample(self):
        """
        Collect a control sample from a live digital forensic situation.
        """
        # Instantiate and revert the virtual machine to a snapshot.
        logger.info("Launching domain and reverting to snapshot")
        domain = emulator.Emulator(self.domain_host, self.domain_name)
        try:
            domain.revert(self.domain_snapshot)
            # Execute the machine and allow it to idle.
            logger.info("Executing domain without user-interaction")
            domain.resume()
            self.idle()
            # Save an image of virtual machine memory.
            logger.info("Saving image of domain memory")
            domain.suspend()
            domain.save_memory(self.image)
            # Terminate the virtual machine.
            logger.info("Terminating domain")
        finally:
            domain.close()

    def idle(self):
        """
        Allow the live digital forensic situation to execute without
        interaction for some period.
        """
        raise NotImplementedError()

    def release(self):
        """
        Release temporary resources.
        """
        logger.info("Unlinking memory image '%s'", self.image)
        if os.path.exists(self.image):
            os.unlink(self.image)
Beispiel #15
0
class FixedDriveAcquisitionVector(interface.Behaviour):
    """
    Fixed local drive live digital forensic acquisition vector
    behaviour.

    Parameters:
    technique.vector.filename -- the name of the disk image file.
    technique.vector.partition -- the index of the partition.
    technique.vector.tag -- the identification tag of the vector.
    technique.vector.image -- the filename of the memory image on the vector.
    
    Slots:
    memory.acquired -- the memory image acquired by the toolkit.
    """

    # Fixed drive acquisition vector parameters.
    vector_filename = interface.Parameter("technique.vector.filename")
    vector_partition = interface.Parameter("technique.vector.partition")
    vector_tag = interface.Parameter("technique.vector.tag")
    vector_image = interface.Parameter("technique.vector.image")

    # Memory image slots.
    image_acquired = interface.Slot("memory.acquired")

    def allocate_behaviour(self):
        """
        Allocate temporary resources.
        """
        super(FixedDriveAcquisitionVector, self).allocate_behaviour()
        logger.info("Allocating resources")
        self.image_acquired = tempfile.mktemp(".memory")

    def attach_vector(self, domain, agent):
        """
        Attach the fixed local drive acquisition vector to the domain.

        Arguments:
        domain -- the virtual machine domain.
        agent -- the virtual machine guest agent.
        """
        logger.info("Verifying that the fixed drive is attached to the domain")
        agent.find(self.vector_tag)

    def detach_vector(self, domain, agent):
        """
        Detach the fixed local drive acquisition vector from the domain.

        Arguments:
        domain -- the virtual machine domain.
        agent -- the virtual machine guest agent.
        """
        logger.info("Flushing the cache from the fixed drive")
        agent.flush(self.vector_tag)

    def transfer_acquired_image(self):
        """
        Transfer the acquired memory image from the acquisition vector
        to the host machine.
        """
        # Attach the disk image and mount the partition on the host machine.
        logger.info("Attaching and mounting the acquisition vector")
        device = volume.attach(self.vector_filename)
        partition_device = volume.partition(device, self.vector_partition)
        volume.wait_for_partition(partition_device)
        path = volume.mount(partition_device)
        # Copy the memory image from the vector partition.
        logger.info("Copying the memory image from the acquisition vector")
        image_filename = os.path.join(path, self.vector_image)
        shutil.copy(image_filename, self.image_acquired)
        # Unmount the partition and detach the disk image.
        logger.info("Unmounting and detaching the acquisition vector")
        volume.unmount(path)
        volume.detach(device)

    def release_behaviour(self):
        """
        Release temporary resources.
        """
        super(FixedDriveAcquisitionVector, self).release_behaviour()
        logger.info("Releasing resources")
        if os.path.exists(self.image_acquired):
            os.unlink(self.image_acquired)