# Volatility
#
# This file is part of Volatility.
#
# Volatility is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# Volatility is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Volatility.  If not, see <http://www.gnu.org/licenses/>.
#

"""
@author:       Bradley Schatz 
@license:      GNU General Public License 2.0
@contact:      bradley@schatzforensic.com.au
@organization: Schatz Forensic
"""

import struct
import volatility.utils as utils
import volatility.scan as scan
import volatility.cache as cache
import volatility.plugins.common as common
import volatility.obj as obj
import volatility.plugins.addrspaces.intel as intel
import volatility.plugins.addrspaces.amd64 as amd64

class KPCRScan(common.AbstractWindowsCommand):
    """Search for and dump potential KPCR values"""

    meta_info = dict(
        author = 'Bradley Schatz',
        copyright = 'Copyright (c) 2010 Bradley Schatz',
        contact = 'bradley@schatzforensic.com.au',
        license = 'GNU General Public License 2.0',
        url = 'http://www.schatzforensic.com.au/',
        os = 'WIN_32_VISTA_SP0',
        version = '1.0',
        )

    @staticmethod
    def register_options(config):
        config.add_option('KPCR', short_option = 'k', default = None, type = 'int',
                          help = "Specify a specific KPCR address")

    @cache.CacheDecorator("tests/kpcrscan")
    def calculate(self):
        """Determines the address space"""
        addr_space = utils.load_as(self._config, astype = 'any')

        scanner = KPCRScanner()
        for offset in scanner.scan(addr_space):
            kpcr = obj.Object("_KPCR", offset = offset, vm = addr_space)
            yield kpcr

    def render_text(self, outfd, data):
        """Renders the KPCR values as text"""

        for kpcr in data:
            outfd.write("*" * 50 + "\n")

            if hasattr(kpcr.obj_vm, 'vtop'):
                outfd.write("{0:<30}: {1:#x}\n".format("Offset (V)", kpcr.obj_offset))
                outfd.write("{0:<30}: {1:#x}\n".format("Offset (P)", kpcr.obj_vm.vtop(kpcr.obj_offset)))
            else:
                outfd.write("{0:<30}: {1:#x}\n".format("Offset (P)", kpcr.obj_offset))

            outfd.write("{0:<30}: {1:#x}\n".format("KdVersionBlock", kpcr.KdVersionBlock))
            outfd.write("{0:<30}: {1:#x}\n".format("IDT", kpcr.IDT))
            outfd.write("{0:<30}: {1:#x}\n".format("GDT", kpcr.GDT))

            current_thread = kpcr.ProcessorBlock.CurrentThread.dereference_as("_ETHREAD")
            idle_thread = kpcr.ProcessorBlock.IdleThread.dereference_as("_ETHREAD")
            next_thread = kpcr.ProcessorBlock.NextThread.dereference_as("_ETHREAD")

            if current_thread:
                outfd.write("{0:<30}: {1:#x} TID {2} ({3}:{4})\n".format(
                    "CurrentThread", 
                    current_thread.obj_offset, current_thread.Cid.UniqueThread, 
                    current_thread.owning_process().ImageFileName, 
                    current_thread.Cid.UniqueProcess, 
                    ))

            if idle_thread:
                outfd.write("{0:<30}: {1:#x} TID {2} ({3}:{4})\n".format(
                    "IdleThread", 
                    idle_thread.obj_offset, idle_thread.Cid.UniqueThread, 
                    idle_thread.owning_process().ImageFileName, 
                    idle_thread.Cid.UniqueProcess, 
                    ))

            if next_thread:
                outfd.write("{0:<30}: {1:#x} TID {2} ({3}:{4})\n".format(
                    "NextThread", 
                    next_thread.obj_offset, 
                    next_thread.Cid.UniqueThread, 
                    next_thread.owning_process().ImageFileName, 
                    next_thread.Cid.UniqueProcess, 
                    ))

            outfd.write("{0:<30}: CPU {1} ({2} @ {3} MHz)\n".format("Details", 
                kpcr.ProcessorBlock.Number, 
                kpcr.ProcessorBlock.VendorString,
                kpcr.ProcessorBlock.MHz))

            outfd.write("{0:<30}: {1:#x}\n".format("CR3/DTB", 
                kpcr.ProcessorBlock.ProcessorState.SpecialRegisters.Cr3))            

class KPCRScannerCheck(scan.ScannerCheck):
    """Checks the self referential pointers to find KPCRs"""
    def __init__(self, address_space):
        scan.ScannerCheck.__init__(self, address_space)
        kpcr = obj.Object("_KPCR", vm = self.address_space, offset = 0)
        if address_space.profile.metadata.get('memory_model', '') == '32bit':
            self.SelfPcr_offset = kpcr.SelfPcr.obj_offset
            self.Prcb_offset = kpcr.Prcb.obj_offset
            self.PrcbData_offset = kpcr.PrcbData.obj_offset
            # In the check() routine, we need to compare masked virtual 
            # addresses, but self.address_space is a BufferAddressSpace. 
            self.address_equality = amd64.AMD64PagedMemory.address_equality
        else:
            # The self-referencing member of _KPCR is Self on x64
            self.SelfPcr_offset = kpcr.Self.obj_offset
            # The pointer to _KPRCB is CurrentPrcb on x64
            self.Prcb_offset = kpcr.CurrentPrcb.obj_offset
            # The nested _KPRCB in Prcb on x64
            self.PrcbData_offset = kpcr.Prcb.obj_offset
            self.address_equality = intel.IA32PagedMemory.address_equality
        self.KPCR = None

    def check(self, offset):
        """ We check that _KCPR.pSelfPCR points to the start of the _KCPR struct """
        paKCPR = offset
        paPRCBDATA = offset + self.PrcbData_offset

        try:
            pSelfPCR = obj.Object('Pointer', offset = (offset + self.SelfPcr_offset), vm = self.address_space)
            pPrcb = obj.Object('Pointer', offset = (offset + self.Prcb_offset), vm = self.address_space)
            if self.address_equality(pSelfPCR, paKCPR) and self.address_equality(pPrcb, paPRCBDATA):
                self.KPCR = pSelfPCR
                return True

        except BaseException:
            return False

        return False

    # make the scan DWORD aligned
    def skip(self, data, offset):
        return 4

        offset_string = struct.pack("I", offset)

        new_offset = offset
        ## A successful match will need to at least match the Most
        ## Significant 3 bytes
        while (new_offset + self.SelfPcr_offset) & 0xFF >= self.SelfPcr_offset:
            new_offset = data.find(offset_string[3], new_offset + 1)
            ## Its not there, skip the whole buffer
            if new_offset < 0:
                return len(data) - offset

            if (new_offset % 4) == 0:
                return new_offset - self.SelfPcr_offset - 1

        return len(data) - offset

class KPCRScanner(scan.BaseScanner):
    checks = [ ("KPCRScannerCheck", {})
               ]
    def scan(self, address_space, offset = 0, maxlen = None):
        return scan.BaseScanner.scan(self, address_space, max(offset, 0x80000000), maxlen)
