# Volatility
#
#
# This program 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.
#
# This program 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 this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
#

"""
@author:       Bradley Schatz 
@license:      GNU General Public License 2.0 or later
@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

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 or later',
        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
        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.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 pSelfPCR == paKCPR and 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)
