# Volatility
# Copyright (C) 2012-13 Volatility Foundation
#
# 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 Version 2 as
# published by the Free Software Foundation.  You may not use, modify or
# distribute this program under any other version of the GNU General
# Public License.
#
# 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/>.
#

#pylint: disable-msg=C0111

import os
import re
import math
import volatility.obj as obj
import volatility.utils as utils
import volatility.debug as debug
import volatility.win32.tasks as tasks_mod
import volatility.win32.modules as modules
import volatility.plugins.common as common
import volatility.plugins.taskmods as taskmods
import json

#--------------------------------------------------------------------------------
# Constants
#--------------------------------------------------------------------------------

PAGE_SIZE = 0x1000
PAGE_MASK = PAGE_SIZE - 1
IMAGE_EXT = "img"
DATA_EXT = "dat"
FILEOFFSET_MASK = 0xFFFFFFFFFFFF0000
VACB_BLOCK = 0x40000
VACB_ARRAY = 0x80
VACB_OFFSET_SHIFT = 18
VACB_LEVEL_SHIFT = 7
VACB_SIZE_OF_FIRST_LEVEL = 1 << (VACB_OFFSET_SHIFT + VACB_LEVEL_SHIFT)

class _CONTROL_AREA(obj.CType):

    def extract_ca_file(self, unsafe = False):
        """ Extracts a file from a specified CONTROL_AREA

        Attempts to extract the memory resident pages pertaining to a
        particular CONTROL_AREA object.

        Args:
            control_area: Instance of a CONTROL_AREA object
            unsafe: Relax safety constraints for more data

        Returns:
            mdata: List of pages, (physoffset, fileoffset, size) tuples, that are memory resident
            zpad: List of pages, (offset, size) tuples, that not memory resident

        Raises:
        """

        zpad = []
        mdata = []

        # Depending on the particular address space being used we need to
        # determine if the MMPTE will be either 4 or 8 bytes. The x64
        # and IA32_PAE both use 8 byte PTEs. Whereas, IA32 uses 4 byte
        # PTE entries.
        memory_model = self.obj_vm.profile.metadata.get('memory_model', '32bit')
        pae = self.obj_vm.pae 

        if pae:
            mmpte_size = self.obj_vm.profile.get_obj_size("_MMPTEPA")
        else:
            mmpte_size = self.obj_vm.profile.get_obj_size("_MMPTE")

        # Calculate the size of the _CONTROL_AREA object. It is used to find
        # the correct offset for the SUBSECTION object and the size of the
        # CONTROL_AREA can differ between versions of Windows.
        control_area_size = self.size()

        # The segment is used to describe the physical view of the
        # file. We also use this as a semantic check to see if
        # the processing should continue. If the Segment address
        # is invalid, then we return.
        Segment = self.Segment
        if not Segment.is_valid():
            return mdata, zpad

        # The next semantic check validates that the _SEGMENT object
        # points back to the appropriate _CONTROL_AREA object. If the
        # check is invalid, then we return.
        if (self.obj_offset != Segment.ControlArea):
            return mdata, zpad

        # This is a semantic check added to make sure the Segment.SizeOfSegment value
        # is consistant with the Segment.TotalNumberOfPtes. This occurs fequently
        # when traversing through CONTROL_AREA Objects (~5%), often leading to
        # impossible values. Thus, to be conservative we do not proceed if the
        # Segment does not seem sound.
        if Segment.SizeOfSegment != (Segment.TotalNumberOfPtes * PAGE_SIZE):
            return mdata, zpad

        # The _SUBSECTION object is typically found immediately following
        # the CONTROL_AREA object. For Image Section Objects, the SUBSECTIONS
        # typically correspond with the sections found in the PE. On the otherhand,
        # for Data Section Objects, there is typically only a single valid SUBSECTION.
        subsection_offset = self.obj_offset + control_area_size
        #subsection = obj.Object("_SUBSECTION", subsection_offset, self.kaddr_space)
        subsection = obj.Object("_SUBSECTION", subsection_offset, self.obj_vm)

        # This was another check which was inspired by Ruud's code. It
        # verifies that the first SubsectionBaase (Mmst) never starts
        # at the beginning of a page. The UNSAFE option allows us to
        # ignore this constraint. This was necessary for dumping file data
        # for file objects found with filescan (ie $Mft)
        SubsectionBase = subsection.SubsectionBase
        if (SubsectionBase & PAGE_MASK == 0x0) and not unsafe:
            return mdata, zpad

        # We obtain the Subsections associated with this file
        # by traversing the singly linked list.  Ideally, this
        # list should be null (0) terminated. Upon occasion we
        # we have seen instances where the link pointers are
        # undefined (XXX). If we hit an invalid pointer, the we
        # we exit the traversal.
        while subsection.is_valid() and subsection.v() != 0x0:

            if not subsection:
                break

            # This constraint makes sure that the _SUBSECTION object
            # points back to the associated CONTROL_AREA object. Otherwise,
            # we exit the traversal.
            if (self.obj_offset != subsection.ControlArea):
                break

            # Extract subsection meta-data into local variables
            # this helps with performance and not having to do
            # repetitive lookups.
            PtesInSubsection = subsection.PtesInSubsection
            SubsectionBase = subsection.SubsectionBase
            NextSubsection = subsection.NextSubsection

            # The offset into the file is stored implicitely
            # based on the PTE's location within the Subsection.
            StartingSector = subsection.StartingSector
            SubsectionOffset = StartingSector * 0x200

            # This was another check based on something Ruud
            # had done.  We also so instances where DataSectionObjects
            # would hit a SubsectionBase that was paged aligned
            # and hit strange data. In those instances, the
            # MMPTE SubsectionAddress would not point to the associated
            # Subsection. (XXX)
            if (SubsectionBase & PAGE_MASK == 0x0) and not unsafe:
                break

            ptecount = 0
            while (ptecount < PtesInSubsection):

                pteoffset = SubsectionBase + (mmpte_size * ptecount)
                FileOffset = SubsectionOffset + ptecount * 0x1000

                #  The size of MMPTE changes depending on if it is IA32 (4 bytes)
                #  or IA32_PAE/AMD64 (8 bytes).
                objname = "_MMPTE"
                if pae:
                    objname = "_MMPTEPA"
                mmpte = obj.Object(objname, offset = pteoffset, vm = \
                    subsection.obj_vm)

                if not mmpte:
                    ptecount += 1
                    continue

                # First we check if the entry is valid. If the entry is valid
                # then we get the physical offset. The valid entries are actually
                # handled by the hardware.
                if mmpte.u.Hard.Valid == 0x1:

                    # There are some valid Page Table entries where bit 63
                    # is used to specify if the page is executable. This is
                    # maintained by the processor. If it is not executable,
                    # then the bit is set. Within the Intel documentation,
                    # this is known as the Execute-disable (XD) flag. Regardless,
                    # we will use the get_phys_addr method from the address space
                    # to obtain the physical address.
                    ### Should we check the size of the PAGE? Haven't seen
                    # a hit for LargePage.
                    #if mmpte.u.Hard.LargePage == 0x1:
                    #    print "LargePage"
                    physoffset = mmpte.u.Hard.PageFrameNumber << 12
                    mdata.append([physoffset, FileOffset, PAGE_SIZE])
                    ptecount += 1
                    continue

                elif mmpte.u.Soft.Prototype == 0x1:
                # If the entry is not a valid physical address then
                # we check if it contains a pointer back to the SUBSECTION
                # object. If so, the page is in the backing file and we will
                # need to pad to maintain spacial integrity of the file. This
                # check needs to be performed for looking for the transition flag.
                # The prototype PTEs are initialized as MMPTE_SUBSECTION with the
                # SubsectionAddress.

                # On x86 systems that use 4 byte MMPTE , the MMPTE_SUBSECTION
                # stores an "encoded" version of the SUBSECTION object address.
                # The data is relative to global variable (MmSubsectionBase or
                # MmNonPagedPoolEnd) depending on the WhichPool member of
                # _SUBSECTION. This applies to x86 systems running ntoskrnl.exe.
                # If bit 10 is set then it is prototype/subsection

                    if (memory_model == "32bit") and not pae:
                        SubsectionOffset = \
                          ((mmpte.u.Subsect.SubsectionAddressHigh << 7) |
                          (mmpte.u.Subsect.SubsectionAddressLow << 3))
                        #WhichPool = mmpte.u.Subsect.WhichPool
                        #print "mmpte 0x%x ptecount 0x%x sub-32 0x%x pteoffset 0x%x which 0x%x subdelta 0x%x"%(mmpte.u.Long,ptecount,subsection_offset,pteoffset,WhichPool,SubsectionOffset)
                        zpad.append([FileOffset, PAGE_SIZE])
                        ptecount += 1
                        continue

                    if memory_model == "64bit" or pae:
                        SubsectionAddress = mmpte.u.Subsect.SubsectionAddress
                    else:
                        SubsectionAddress = mmpte.u.Long

                    if SubsectionAddress == subsection.obj_offset:
                        # sub proto/prot 4c0 420
                        #print "mmpte 0x%x ptecount 0x%x sub 0x%x offset 0x%x"%(mmpte.u.Long,ptecount,SubsectionAddress,pteoffset)
                        zpad.append([FileOffset, PAGE_SIZE])
                        ptecount += 1
                        continue
                    elif (SubsectionAddress == (subsection.obj_offset + 4)):
                        # This was a special case seen on IA32_PAE systems where
                        # the SubsectionAddress pointed to subsection.obj_offset+4
                        # (0x420, 0x460, 0x4a0)

                        #print "mmpte 0x%x ptecount 0x%x sub+4 0x%x offset 0x%x"%(mmpte.u.Long,ptecount,SubsectionAddress,pteoffset)
                        zpad.append([FileOffset, PAGE_SIZE])
                        ptecount += 1
                        continue
                    else:
                        #print "mmpte 0x%x ptecount 0x%x sub_unk 0x%x offset 0x%x suboffset 0x%x"%(mmpte.u.Long,ptecount,SubsectionAddress,pteoffset,subsection.obj_offset)
                        zpad.append([FileOffset, PAGE_SIZE])
                        ptecount += 1
                        continue

                # Check if the entry is a DemandZero entry.
                elif (mmpte.u.Soft.Transition == 0x0):
                    if ((mmpte.u.Soft.PageFileLow == 0x0) and
                     (mmpte.u.Soft.PageFileHigh == 0x0)):
                        # Example entries include: a0,e0
                        #print "mmpte 0x%x ptecount 0x%x zero offset 0x%x subsec 0x%x"%(mmpte.u.Long,ptecount,pteoffset,subsection.obj_offset)
                        zpad.append([FileOffset, PAGE_SIZE])
                        ptecount += 1
                    else:
                        #print "mmpte 0x%x ptecount 0x%x paged offset 0x%x subsec 0x%x file 0x%x offset 0x%x"%(mmpte.u.Long,ptecount,pteoffset,subsection.obj_offset,mmpte.u.Soft.PageFileLow,mmpte.u.Soft.PageFileHigh)

                        zpad.append([FileOffset, PAGE_SIZE])
                        ptecount += 1

                # If the entry is not a valid physical address then
                # we also check to see if it is in transition.
                elif mmpte.u.Trans.Transition == 0x1:
                    physoffset = mmpte.u.Trans.PageFrameNumber << 12
                    #print "mmpte 0x%x ptecount 0x%x transition 0x%x offset 0x%x"%(mmpte.u.Long,ptecount,physoffset,pteoffset)

                    mdata.append([physoffset, FileOffset, PAGE_SIZE])
                    ptecount += 1
                    continue
                else:
                    # This is a catch all for all the other entry types.
                    # sub proto/pro 420,4e0,460,4a0 (x64 +0x28)(x32 +4)
                    # other a0,e0,0, (20,60)
                    # 0x80000000
                    #print "mmpte 0x%x ptecount 0x%x other offset 0x%x subsec 0x%x"%(mmpte.u.Long,ptecount,pteoffset,subsection.obj_offset)
                    zpad.append([FileOffset, PAGE_SIZE])
                    ptecount += 1

	    # Traverse the singly linked list to its next member.
            subsection = NextSubsection

        return (mdata, zpad)

class _SHARED_CACHE_MAP(obj.CType):

    def is_valid(self):
        if not obj.CType.is_valid(self):
            return False

        # Added a semantic check to make sure the data is in a sound state. It's better
        # to catch it early.
        FileSize = self.FileSize.QuadPart
        ValidDataLength = self.ValidDataLength.QuadPart
        SectionSize = self.SectionSize.QuadPart
       
        # Corrupted values: Win2003SP0x86.vmem 
        if FileSize <= 0 or ValidDataLength <= 0:
            return False

        #print "SectionSize 0x%x < 0 or FileSize < 0x%x ValidDataLength 0x%x"%(SectionSize,FileSize,ValidDataLength)
        #if SectionSize < 0 or (FileSize < ValidDataLength):
        if SectionSize < 0 or ((FileSize < ValidDataLength) and (ValidDataLength != 0x7fffffffffffffff)):
            return False

        return True

    def process_index_array(self, array_pointer, level, limit, vacbary = None):

        """ Recursively process the sparse multilevel VACB index array

        Args:
            array_pointer:    The address of a possible index array
            shared_cache_map: The associated SHARED_CACHE_MAP object
            level:            The current level
            limit:            The level where we abandon all hope. Ideally this is 7
            vacbary:          An array of collected VACBs

        Returns:
            vacbary:          Collected VACBs 
        """
        if vacbary is None:
            vacbary = []

        if level > limit:
            return []

        # Create an array of VACB entries
        VacbArray = obj.Object("Array", offset = array_pointer, \
            vm = self.obj_vm, count = VACB_ARRAY, \
            targetType = "address", parent = self)

        # Iterate through the entries
        for _i in range(0, VACB_ARRAY):
            # Check if the VACB entry is in use
            if VacbArray[_i] == 0x0:
                continue

            Vacbs = obj.Object("_VACB", offset = int(VacbArray[_i]), vm = self.obj_vm)

            # Check if this is a valid VACB entry by verifying
            # the SharedCacheMap member.
            if Vacbs.SharedCacheMap == self.obj_offset:
                # This is a VACB associated with this cache map
                vacbinfo = self.extract_vacb(Vacbs, VACB_BLOCK)
                if vacbinfo:
                    vacbary.append(vacbinfo)
            else:
                #Process the next level of the multi-level array
                vacbary = self.process_index_array(VacbArray[_i], level + 1, limit, vacbary)
                #vacbary = vacbary + _vacbary
        return vacbary

    def extract_vacb(self, vacbs, size):
        """ Extracts data from a specified VACB

        Attempts to extract the memory resident data from a specified
        VACB.

        Args:
            vacbs: 		The VACB object
            size: 		How much data should be read from the VACB 
            shared_cache_map: 	The associated SHARED_CACHE_MAP object

        Returns:
            vacbinfo:    	Extracted VACB meta-information

        """
        # This is used to collect summary information. We will eventually leverage this
        # when creating the externally exposed APIs.
        vacbinfo = {}

        # Check if the Overlay member of _VACB is resident
        # The Overlay member stores information about the FileOffset
        # and the ActiveCount. This is just another proactive check
        # to make sure the objects are seemingly sound.
        if not vacbs.Overlay:
            return vacbinfo

        # We should add another check to make sure that
        # the SharedCacheMap member of the VACB points back
        # to the corresponding SHARED_CACHE_MAP
        if vacbs.SharedCacheMap != self.v():
            return vacbinfo

        # The FileOffset member of VACB is used to denote the
        # offset within the file where the view begins.  Since all
        # views are 256 KB in size, the bottom 16 bits are used to
        # store the number of references to the view.
        FileOffset = vacbs.Overlay.FileOffset.QuadPart

        if not FileOffset:
            return vacbinfo

        ActiveCount = vacbs.Overlay.ActiveCount
        FileOffset = FileOffset & FILEOFFSET_MASK
        BaseAddress = vacbs.BaseAddress.v()

        vacbinfo['foffset'] = int(FileOffset)
        vacbinfo['acount'] = int(ActiveCount)
        vacbinfo['voffset'] = int(vacbs.obj_offset)
        vacbinfo['baseaddr'] = int(BaseAddress)
        vacbinfo['size'] = int(size)

        return vacbinfo

    def extract_scm_file(self):
        """ Extracts a file from a specified _SHARED_CACHE_MAP

        Attempts to extract the memory resident pages pertaining to a
        particular _SHARED_CACHE_MAP object.

        Args:
            shared_cache_map: Instance of a _SHARED_CACHE_MAP object

        Returns:
            vacbary: List of collected VACB meta information.

        Raises:

        """

        vacbary = []

        if self.obj_offset == 0x0:
            return

        # Added a semantic check to make sure the data is in a sound state.
        #FileSize = shared_cache_map.FileSize.QuadPart
        #ValidDataLength = shared_cache_map.ValidDataLength.QuadPart
        SectionSize = self.SectionSize.QuadPart

        # Let's begin by determining the number of Virtual Address Control
        # Blocks (VACB) that are stored within the cache (nonpaged). A VACB
        # represents one 256-KB view in the system cache. There a are a couple
        # options to use for the data size: ValidDataLength, FileSize,
        # and SectionSize.
        full_blocks = SectionSize / VACB_BLOCK
        left_over = SectionSize % VACB_BLOCK

        # As an optimization, the shared cache map object contains a VACB index
        # array of four entries.  The VACB index arrays are arrays of pointers
        # to VACBs, that track which views of a given file are mapped in the cache.
        # For example, the first entry in the VACB index array refers to the first
        # 256 KB of the file. The InitialVacbs can describe a file up to 1 MB (4xVACB).
        iterval = 0
        while (iterval < full_blocks) and (full_blocks <= 4):
            Vacbs = self.InitialVacbs[iterval]
            vacbinfo = self.extract_vacb(Vacbs, VACB_BLOCK)
            if vacbinfo: vacbary.append(vacbinfo)
            iterval += 1

        # We also have to account for the spill over data
        # that is not found in the full blocks.  The first case to
        # consider is when the spill over is still in InitialVacbs.
        if (left_over > 0) and (full_blocks < 4):
            Vacbs = self.InitialVacbs[iterval]
            vacbinfo = self.extract_vacb(Vacbs, left_over)
            if vacbinfo: vacbary.append(vacbinfo)

        # If the file is larger than 1 MB, a seperate VACB index array
        # needs to be allocated. This is based on how many 256 KB blocks
        # would be required for the size of the file. This newly allocated
        # VACB index array is found through the Vacbs member of
        # SHARED_CACHE_MAP.

        Vacbs = self.Vacbs

        if not Vacbs or (Vacbs.v() == 0):
            return vacbary

        # There are a number of instances where the initial value in
        # InitialVacb will also be the fist entry in Vacbs. Thus we
        # ignore, since it was already processed. It is possible to just
        # process again as the file offset is specified for each VACB.
        if  self.InitialVacbs[0].obj_offset == Vacbs.v():
            return vacbary

        # If the file is less than 32 MB than it can be found in
        # a single level VACB index array.
        size_of_pointer = self.obj_vm.profile.get_obj_size("address")

        if not SectionSize > VACB_SIZE_OF_FIRST_LEVEL:

            ArrayHead = Vacbs.v()
            _i = 0
            for _i in range(0, full_blocks):
                vacb_addr = ArrayHead + (_i * size_of_pointer)
                vacb_entry = obj.Object("address", offset = vacb_addr, vm = Vacbs.obj_vm)

                # If we find a zero entry, then we proceed to the next one.
                # If the entry is zero, then the view is not mapped and we
                # skip. We do not pad because we use the FileOffset to seek
                # to the correct offset in the file.
                if not vacb_entry or (vacb_entry.v() == 0x0):
                    continue
                Vacb = obj.Object("_VACB", offset = vacb_entry.v(), vm = self.obj_vm)
                vacbinfo = self.extract_vacb(Vacb, VACB_BLOCK)
                if vacbinfo:
                    vacbary.append(vacbinfo)
            if left_over > 0:
                vacb_addr = ArrayHead + ((_i + 1) * size_of_pointer)
                vacb_entry = obj.Object("address", offset = vacb_addr, vm = Vacbs.obj_vm)

                if not vacb_entry or (vacb_entry.v() == 0x0):
                    return vacbary

                Vacb = obj.Object("_VACB", offset = vacb_entry.v(), vm = self.obj_vm)
                vacbinfo = self.extract_vacb(Vacb, left_over)
                if vacbinfo:
                    vacbary.append(vacbinfo)
            # The file is less than 32 MB, so we can
            # stop processing.
            return vacbary

        # If we get to this point, then we know that the SectionSize is greator than
        # VACB_SIZE_OF_FIRST_LEVEL (32 MB). Then we have a "sparse multilevel index
        # array where each VACB index array is made up of 128 entries. We no
        # longer assume the data is sequential. (Log2 (32 MB) - 18)/7

        #tree_depth = math.ceil((math.ceil(math.log(file_size, 2)) - 18)/7)
        level_depth = math.ceil(math.log(SectionSize, 2))
        level_depth = (level_depth - VACB_OFFSET_SHIFT) / VACB_LEVEL_SHIFT
        level_depth = math.ceil(level_depth)
        limit_depth = level_depth

        if SectionSize > VACB_SIZE_OF_FIRST_LEVEL:

            # Create an array of 128 entries for the VACB index array
            VacbArray = obj.Object("Array", offset = Vacbs.v(), \
                vm = self.obj_vm, count = VACB_ARRAY, \
                targetType = "address", parent = self)

            # We use a bit of a brute force method. We walk the
            # array and if any entry points to the shared cache map
            # object then we extract it. Otherwise, if it is non-zero
            # we attempt to traverse to the next level.
            for _i in range(0, VACB_ARRAY):
                if VacbArray[_i] == 0x0:
                    continue
                Vacb = obj.Object("_VACB", offset = int(VacbArray[_i]), vm = self.obj_vm)
                if  Vacb.SharedCacheMap == self.obj_offset:
                    vacbinfo = self.extract_vacb(Vacb, VACB_BLOCK)
                    if vacbinfo:
                        vacbary.append(vacbinfo)
                else:
                    # The Index is a pointer
                    #Process the next level of the multi-level array
                    # We set the limit_depth to be the depth of the tree
                    # as determined from the size and we initialize the
                    # current level to 2.
                    vacbary = self.process_index_array(VacbArray[_i], 2, limit_depth, vacbary)
                    #vacbary = vacbary + _vacbary

        return vacbary

class ControlAreaModification(obj.ProfileModification):
    conditions = {'os': lambda x: x == 'windows'}

    def modification(self, profile):
        profile.object_classes.update({
            '_CONTROL_AREA': _CONTROL_AREA,
            '_SHARED_CACHE_MAP': _SHARED_CACHE_MAP,
            })

#--------------------------------------------------------------------------------
# VTypes
#--------------------------------------------------------------------------------

# Windows x86 symbols for ntkrnlpa
ntkrnlpa_types_x86 = {
    '__ntkrnlpa' : [ 0x8, {
    'Long' : [ 0x0, ['unsigned long long']],
    'VolatileLong' : [ 0x0, ['unsigned long long']],
    'Hard' : [ 0x0, ['_MMPTE_HARDWARE_64']],
    'Flush' : [ 0x0, ['_HARDWARE_PTE']],
    'Proto' : [ 0x0, ['_MMPTE_PROTOTYPE']],
    'Soft' : [ 0x0, ['_MMPTE_SOFTWARE_64']],
    'TimeStamp' : [ 0x0, ['_MMPTE_TIMESTAMP']],
    'Trans' : [ 0x0, ['_MMPTE_TRANSITION_64']],
    'Subsect' : [ 0x0, ['_MMPTE_SUBSECTION_64']],
    'List' : [ 0x0, ['_MMPTE_LIST']],
    } ],
    '_MMPTEPA' : [ 0x8, {
    'u' : [ 0x0, ['__ntkrnlpa']],
    } ],
    '_MMPTE_SUBSECTION_64' : [ 0x8, {
    'Valid' : [ 0x0, ['BitField', dict(start_bit = 0, end_bit = 1, native_type = 'unsigned long long')]],
    'Unused0' : [ 0x0, ['BitField', dict(start_bit = 1, end_bit = 5, native_type = 'unsigned long long')]],
    'Protection' : [ 0x0, ['BitField', dict(start_bit = 5, end_bit = 10, native_type = 'unsigned long long')]],
    'Prototype' : [ 0x0, ['BitField', dict(start_bit = 10, end_bit = 11, native_type = 'unsigned long long')]],
    'Unused1' : [ 0x0, ['BitField', dict(start_bit = 11, end_bit = 32, native_type = 'unsigned long long')]],
    'SubsectionAddress' : [ 0x0, ['BitField', dict(start_bit = 32, end_bit = 64, native_type = 'long long')]],
    } ],
    '_MMPTE_TRANSITION_64' : [ 0x8, {
    'Valid' : [ 0x0, ['BitField', dict(start_bit = 0, end_bit = 1, native_type = 'unsigned long long')]],
    'Write' : [ 0x0, ['BitField', dict(start_bit = 1, end_bit = 2, native_type = 'unsigned long long')]],
    'Owner' : [ 0x0, ['BitField', dict(start_bit = 2, end_bit = 3, native_type = 'unsigned long long')]],
    'WriteThrough' : [ 0x0, ['BitField', dict(start_bit = 3, end_bit = 4, native_type = 'unsigned long long')]],
    'CacheDisable' : [ 0x0, ['BitField', dict(start_bit = 4, end_bit = 5, native_type = 'unsigned long long')]],
    'Protection' : [ 0x0, ['BitField', dict(start_bit = 5, end_bit = 10, native_type = 'unsigned long long')]],
    'Prototype' : [ 0x0, ['BitField', dict(start_bit = 10, end_bit = 11, native_type = 'unsigned long long')]],
    'Transition' : [ 0x0, ['BitField', dict(start_bit = 11, end_bit = 12, native_type = 'unsigned long long')]],
    'PageFrameNumber' : [ 0x0, ['BitField', dict(start_bit = 12, end_bit = 48, native_type = 'unsigned long long')]],
    'Unused' : [ 0x0, ['BitField', dict(start_bit = 48, end_bit = 64, native_type = 'unsigned long long')]],
    }],
   '_MMPTE_HARDWARE_64' : [ 0x8, {
    'Valid' : [ 0x0, ['BitField', dict(start_bit = 0, end_bit = 1, native_type = 'unsigned long long')]],
    'Dirty1' : [ 0x0, ['BitField', dict(start_bit = 1, end_bit = 2, native_type = 'unsigned long long')]],
    'Owner' : [ 0x0, ['BitField', dict(start_bit = 2, end_bit = 3, native_type = 'unsigned long long')]],
    'WriteThrough' : [ 0x0, ['BitField', dict(start_bit = 3, end_bit = 4, native_type = 'unsigned long long')]],
    'CacheDisable' : [ 0x0, ['BitField', dict(start_bit = 4, end_bit = 5, native_type = 'unsigned long long')]],
    'Accessed' : [ 0x0, ['BitField', dict(start_bit = 5, end_bit = 6, native_type = 'unsigned long long')]],
    'Dirty' : [ 0x0, ['BitField', dict(start_bit = 6, end_bit = 7, native_type = 'unsigned long long')]],
    'LargePage' : [ 0x0, ['BitField', dict(start_bit = 7, end_bit = 8, native_type = 'unsigned long long')]],
    'Global' : [ 0x0, ['BitField', dict(start_bit = 8, end_bit = 9, native_type = 'unsigned long long')]],
    'CopyOnWrite' : [ 0x0, ['BitField', dict(start_bit = 9, end_bit = 10, native_type = 'unsigned long long')]],
    'Unused' : [ 0x0, ['BitField', dict(start_bit = 10, end_bit = 11, native_type = 'unsigned long long')]],
    'Write' : [ 0x0, ['BitField', dict(start_bit = 11, end_bit = 12, native_type = 'unsigned long long')]],
    'PageFrameNumber' : [ 0x0, ['BitField', dict(start_bit = 12, end_bit = 48, native_type = 'unsigned long long')]],
    'reserved1' : [ 0x0, ['BitField', dict(start_bit = 48, end_bit = 52, native_type = 'unsigned long long')]],
    'SoftwareWsIndex' : [ 0x0, ['BitField', dict(start_bit = 52, end_bit = 63, native_type = 'unsigned long long')]],
    'NoExecute' : [ 0x0, ['BitField', dict(start_bit = 63, end_bit = 64, native_type = 'unsigned long long')]],
    } ],
    '_MMPTE_SOFTWARE_64' : [ 0x8, {
    'Valid' : [ 0x0, ['BitField', dict(start_bit = 0, end_bit = 1, native_type = 'unsigned long long')]],
    'PageFileLow' : [ 0x0, ['BitField', dict(start_bit = 1, end_bit = 5, native_type = 'unsigned long long')]],
    'Protection' : [ 0x0, ['BitField', dict(start_bit = 5, end_bit = 10, native_type = 'unsigned long long')]],
    'Prototype' : [ 0x0, ['BitField', dict(start_bit = 10, end_bit = 11, native_type = 'unsigned long long')]],
    'Transition' : [ 0x0, ['BitField', dict(start_bit = 11, end_bit = 12, native_type = 'unsigned long long')]],
    'UsedPageTableEntries' : [ 0x0, ['BitField', dict(start_bit = 12, end_bit = 22, native_type = 'unsigned long long')]],
    'InStore' : [ 0x0, ['BitField', dict(start_bit = 22, end_bit = 23, native_type = 'unsigned long long')]],
    'Reserved' : [ 0x0, ['BitField', dict(start_bit = 23, end_bit = 32, native_type = 'unsigned long long')]],
    'PageFileHigh' : [ 0x0, ['BitField', dict(start_bit = 32, end_bit = 64, native_type = 'unsigned long long')]],
    } ],
}

class DumpFilesVTypesx86(obj.ProfileModification):
    """This modification applies the vtypes for all 
    versions of 32bit Windows."""
    before = ['WindowsObjectClasses']
    conditions = {'os': lambda x: x == 'windows',
                  'memory_model': lambda x : x == '32bit'}
    def modification(self, profile):
        profile.vtypes.update(ntkrnlpa_types_x86)

class DumpFiles(common.AbstractWindowsCommand):
    """Extract memory mapped and cached files"""

    def __init__(self, config, *args, **kwargs):
        common.AbstractWindowsCommand.__init__(self, config, *args, **kwargs)

        self.kaddr_space = None
        self.filters = []

        config.add_option('REGEX', short_option = 'r',
                      help = 'Dump files matching REGEX',
                      action = 'store', type = 'string')
        config.add_option('IGNORE-CASE', short_option = 'i',
                      help = 'Ignore case in pattern match',
                      action = 'store_true', default = False)
        config.add_option('OFFSET', short_option = 'o', default = None,
                      help = 'Dump files for Process with physical address OFFSET',
                      action = 'store', type = 'int')
        config.add_option('PHYSOFFSET', short_option = 'Q', default = None,
                      help = 'Dump File Object at physical address PHYSOFFSETs (comma delimited)',
                      action = 'store', type = 'str')
        config.add_option('DUMP-DIR', short_option = 'D', default = None,
                      cache_invalidator = False,
                      help = 'Directory in which to dump extracted files')
        config.add_option('SUMMARY-FILE', short_option = 'S', default = None,
                      cache_invalidator = False,
                      help = 'File where to store summary information')
        config.add_option('PID', short_option = 'p', default = None,
                      help = 'Operate on these Process IDs (comma-separated)',
                      action = 'store', type = 'str')
        config.add_option('NAME', short_option = 'n',
                      help = 'Include extracted filename in output file path',
                      action = 'store_true', default = False)
        config.add_option('UNSAFE', short_option = 'u',
                      help = 'Relax safety constraints for more data',
                      action = 'store_true', default = False)

        # Possible filters include:
        # SharedCacheMap,DataSectionObject,ImageSectionObject,HandleTable,VAD
        config.add_option("FILTER", short_option = 'F', default = None,
                            help = 'Filters to apply (comma-separated). Possible values:\n\nSharedCacheMap,DataSectionObject,ImageSectionObject,HandleTable,VAD')

    def filter_tasks(self, tasks):
        """ Reduce the tasks based on the user selectable PIDS parameter.

        Returns a reduced list or the full list if config.PIDS not specified.
        """

        if self._config.PID is None:
            return tasks

        try:
            pidlist = [int(p) for p in self._config.PID.split(',')]
        except ValueError:
            debug.error("Invalid PID {0}".format(self._config.PID))

        return [t for t in tasks if t.UniqueProcessId in pidlist]

    def audited_read_bytes(self, vm, vaddr, length, pad):
        """ This function provides an audited zread capability

        It performs a similar function to zread, in that it will
        pad "invalid" pages.  The main difference is that it allows
        us to collect auditing information about which pages were actually 
        present and which ones were padded. 

        Args:
            vm: The address space to read the data from. 
            vaddr: The virtual address to start reading the data from.
            length: How many bytes to read
            pad: This argument controls if the unavailable bytes are padded.

        Returns:
            ret: Data that was read
            mdata: List of pages that are memory resident
            zpad: List of pages that not memory resident

        Raises:

        """

        zpad = []
        mdata = []

        vaddr, length = int(vaddr), int(length)

        ret = ''

        while length > 0:
            chunk_len = min(length, PAGE_SIZE - (vaddr % PAGE_SIZE))

            buf = vm.read(vaddr, chunk_len)
            if vm.vtop(vaddr) is None:
                zpad.append([vaddr, chunk_len])
                if pad:
                    buf = '\x00' * chunk_len
                else:
                    buf = ''
            else:
                mdata.append([vaddr, chunk_len])

            ret += buf
            vaddr += chunk_len
            length -= chunk_len

        return ret, mdata, zpad

    def calculate(self):
        """ Finds all the requested FILE_OBJECTS  
        
        Traverses the VAD and HandleTable to find all requested
        FILE_OBJECTS

        """
        # Initialize containers for collecting artifacts.
        control_area_list = []
        shared_maps = []
        procfiles = []

        # These lists are used for object collecting files from
        # both the VAD and handle tables
        vadfiles = []
        handlefiles = []

        # Determine which filters the user wants to see
        self.filters = []
        if self._config.FILTER:
            self.filters = self._config.FILTER.split(',')

        # Instantiate the kernel address space
        self.kaddr_space = utils.load_as(self._config)

        # Check to see if the physical address offset was passed for a
        # particular process. Otherwise, use the whole task list.
        if self._config.OFFSET != None:
            tasks_list = [taskmods.DllList.virtual_process_from_physical_offset(
                self.kaddr_space, self._config.OFFSET)]
        else:
            # Filter for the specified processes
            tasks_list = self.filter_tasks(tasks_mod.pslist(self.kaddr_space))

        # If a regex is specified, build it.
        if self._config.REGEX:
            try:
                if self._config.IGNORE_CASE:
                    file_re = re.compile(self._config.REGEX, re.I)
                else:
                    file_re = re.compile(self._config.REGEX)
            except re.error, e:
                debug.error('Error parsing regular expression: {0:s}'.format(e))

        # Check to see if a specific physical address was specified for a
        # FILE_OBJECT. In particular, this is useful for FILE_OBJECTS that
        # are found with filescan that are not associated with a process
        # For example, $Mft.
        if self._config.PHYSOFFSET:
            try:
                phys = []
                for p in self._config.PHYSOFFSET.split(","):
                    file_obj = obj.Object("_FILE_OBJECT", int(p, 16), self.kaddr_space.base, native_vm = self.kaddr_space)
                    phys.append(file_obj)
                procfiles.append((None, phys))
            except ValueError:
                debug.error("Invalid PHYSOFFSET {0}".format(self._config.PHYSOFFSET))

        # Iterate through the process list and collect all references to
        # FILE_OBJECTS from both the VAD and HandleTable. Each open handle to a file
        # has a corresponding FILE_OBJECT.
        if not self._config.PHYSOFFSET:
            for task in tasks_list:
                pid = task.UniqueProcessId

                # Extract FILE_OBJECTS from the VAD
                if not self.filters or "VAD" in self.filters:
                    for vad in task.VadRoot.traverse():
                        if vad != None:
                            try:
                                control_area = vad.ControlArea
                                if not control_area:
                                    continue
                                file_object = vad.FileObject
                                if file_object:

                                    # Filter for specific FILE_OBJECTS based on user defined
                                    # regular expression. (Performance optimization)
                                    if self._config.REGEX:
                                        name = None
                                        if file_object.FileName:
                                            name = str(file_object.file_name_with_device())
                                        if not name:
                                            continue
                                        if not file_re.search(name):
                                            continue

                                    vadfiles.append(file_object)
                            except AttributeError:
                                pass

                if not self.filters or "HandleTable" in self.filters:
                    # Extract the FILE_OBJECTS from the handle table
                    if task.ObjectTable.HandleTableList:
                        for handle in task.ObjectTable.handles():
                            otype = handle.get_object_type()
                            if otype == "File":
                                file_obj = handle.dereference_as("_FILE_OBJECT")

                                if file_obj:

                                    # Filter for specific FILE_OBJECTS based on user defined
                                    # regular expression. (Performance Optimization)
                                    if self._config.REGEX:
                                        name = None
                                        if file_obj.FileName:
                                            name = str(file_obj.file_name_with_device())
                                        if not name:
                                            continue
                                        if not file_re.search(name):
                                            continue

                                    handlefiles.append(file_obj)

                # Append the lists of file objects
                #allfiles = handlefiles + vadfiles
                procfiles.append((pid, handlefiles + vadfiles))

        for pid, allfiles in procfiles:
            for file_obj in allfiles:

                # XXX TODO: remove these comments when accepted
                #if not self._config.PHYSOFFSET:
                offset = file_obj.obj_offset
                #else:
                # I'm not sure why we need to specify PHYSOFFSET here, 
                # shouldn't we have a valid _FILE_OBJECT?
                #    offset = self._config.PHYSOFFSET

                name = None

                if file_obj.FileName:
                    name = str(file_obj.file_name_with_device())

                # The SECTION_OBJECT_POINTERS structure is used by the memory
                # manager and cache manager to store file-mapping and cache information
                # for a particular file stream. We will use it to determine what type
                # of FILE_OBJECT we have and how it should be parsed.
                if file_obj.SectionObjectPointer:
                    DataSectionObject = \
                        file_obj.SectionObjectPointer.DataSectionObject
                    SharedCacheMap = \
                        file_obj.SectionObjectPointer.SharedCacheMap
                    ImageSectionObject = \
                        file_obj.SectionObjectPointer.ImageSectionObject

                    # The ImageSectionObject is used to track state information for
                    # an executable file stream. We will use it to extract memory
                    # mapped binaries.

                    if not self.filters or "ImageSectionObject" in self.filters:

                        if ImageSectionObject and ImageSectionObject != 0:
                            summaryinfo = {}
                            # It points to a image section object( CONTROL_AREA )
                            control_area = \
                                ImageSectionObject.dereference_as('_CONTROL_AREA')

                            if not control_area in control_area_list:
                                control_area_list.append(control_area)

                                # The format of the filenames: file.<pid>.<control_area>.[img|dat]
                                ca_offset_string = "0x{0:x}".format(control_area.obj_offset)
                                if self._config.NAME and name != None:
                                    fname = name.split("\\")
                                    ca_offset_string += "." + fname[-1]
                                file_string = ".".join(["file", str(pid), ca_offset_string, IMAGE_EXT])
                                of_path = os.path.join(self._config.DUMP_DIR, file_string)
                                (mdata, zpad) = control_area.extract_ca_file(self._config.UNSAFE)
                                summaryinfo['name'] = name
                                summaryinfo['type'] = "ImageSectionObject"
                                if pid:
                                    summaryinfo['pid'] = int(pid)
                                else:
                                    summaryinfo['pid'] = None
                                summaryinfo['present'] = mdata
                                summaryinfo['pad'] = zpad
                                summaryinfo['fobj'] = int(offset)
                                summaryinfo['ofpath'] = of_path
                                yield summaryinfo

                    # The DataSectionObject is used to track state information for
                    # a data file stream. We will use it to extract artifacts of
                    # memory mapped data files.

                    if not self.filters or "DataSectionObject" in self.filters:

                        if DataSectionObject and DataSectionObject != 0:
                            summaryinfo = {}
                            # It points to a data section object (CONTROL_AREA)
                            control_area = DataSectionObject.dereference_as('_CONTROL_AREA')

                            if not control_area in control_area_list:
                                control_area_list.append(control_area)

                                # The format of the filenames: file.<pid>.<control_area>.[img|dat]
                                ca_offset_string = "0x{0:x}".format(control_area.obj_offset)
                                if self._config.NAME and name != None:
                                    fname = name.split("\\")
                                    ca_offset_string += "." + fname[-1]
                                file_string = ".".join(["file", str(pid), ca_offset_string, DATA_EXT])
                                of_path = os.path.join(self._config.DUMP_DIR, file_string)

                                (mdata, zpad) = control_area.extract_ca_file(self._config.UNSAFE)
                                summaryinfo['name'] = name
                                summaryinfo['type'] = "DataSectionObject"
                                if pid:
                                    summaryinfo['pid'] = int(pid)
                                else:
                                    summaryinfo['pid'] = None
                                summaryinfo['present'] = mdata
                                summaryinfo['pad'] = zpad
                                summaryinfo['fobj'] = int(offset)
                                summaryinfo['ofpath'] = of_path
                                yield summaryinfo

                    # The SharedCacheMap is used to track views that are mapped to the
                    # data file stream. Each cached file has a single SHARED_CACHE_MAP object,
                    # which has pointers to slots in the system cache which contain views of the file.
                    # The shared cache map is used to describe the state of the cached file.
                    if self.filters and "SharedCacheMap" not in self.filters:
                        continue

                    if SharedCacheMap:
                        vacbary = []
                        summaryinfo = {}
                        #The SharedCacheMap member points to a SHARED_CACHE_MAP object.
                        shared_cache_map = SharedCacheMap.dereference_as('_SHARED_CACHE_MAP')
                        if shared_cache_map.obj_offset == 0x0:
                            continue

                        # Added a semantic check to make sure the data is in a sound state. It's better
                        # to catch it early.
                        if not shared_cache_map.is_valid():
                            continue

                        if not shared_cache_map.obj_offset in shared_maps:
                            shared_maps.append(shared_cache_map.obj_offset)
                        else:
                            continue

                        shared_cache_map_string = ".0x{0:x}".format(shared_cache_map.obj_offset)
                        if self._config.NAME and name != None:
                            fname = name.split("\\")
                            shared_cache_map_string = shared_cache_map_string + "." + fname[-1]
                        of_path = os.path.join(self._config.DUMP_DIR, "file." + str(pid) + shared_cache_map_string + ".vacb")

                        vacbary = shared_cache_map.extract_scm_file()

                        summaryinfo['name'] = name
                        summaryinfo['type'] = "SharedCacheMap"
                        if pid:
                            summaryinfo['pid'] = int(pid)
                        else:
                            summaryinfo['pid'] = None
                        summaryinfo['fobj'] = int(offset)
                        summaryinfo['ofpath'] = of_path
                        summaryinfo['vacbary'] = vacbary
                        yield summaryinfo

    def render_text(self, outfd, data):
        """Renders output for the dumpfiles plugin. 

        This includes extracting the file artifacts from memory 
        to the specified dump directory.

        Args:
            outfd: The file descriptor to write the text to.
            data:  (summaryinfo)

        """

        # Summary file object
        summaryfo = None
        summaryinfo = data

        if self._config.DUMP_DIR == None:
            debug.error("Please specify a dump directory (--dump-dir)")
        if not os.path.isdir(self._config.DUMP_DIR):
            debug.error(self._config.DUMP_DIR + " is not a directory")

        if self._config.SUMMARY_FILE:
            summaryfo = open(self._config.SUMMARY_FILE, 'wb')

        for summaryinfo in data:

            if summaryinfo['type'] == "DataSectionObject":

                outfd.write("DataSectionObject {0:#010x}   {1:<6} {2}\n".format(summaryinfo['fobj'], summaryinfo['pid'], summaryinfo['name']))
                if len(summaryinfo['present']) == 0:
                    continue

                of = open(summaryinfo['ofpath'], 'wb')

                for mdata in summaryinfo['present']:
                    rdata = None
                    if not mdata[0]:
                        continue

                    try:
                        rdata = self.kaddr_space.base.read(mdata[0], mdata[2])
                    except (IOError, OverflowError):
                        debug.debug("IOError: Pid: {0} File: {1} PhysAddr: {2} Size: {3}".format(summaryinfo['pid'], summaryinfo['name'], mdata[0], mdata[2]))

                    if not rdata:
                        continue

                    of.seek(mdata[1])
                    of.write(rdata)
                    continue
                # XXX Verify FileOffsets
                #for zpad in summaryinfo['pad']:
                #    of.seek(zpad[0])
                #    of.write("\0" * zpad[1])

                if self._config.SUMMARY_FILE:
                    json.dump(summaryinfo, summaryfo)
                    summaryfo.write("\n")
                of.close()

            elif summaryinfo['type'] == "ImageSectionObject":
                outfd.write("ImageSectionObject {0:#010x}   {1:<6} {2}\n".format(summaryinfo['fobj'], summaryinfo['pid'], summaryinfo['name']))

                if len(summaryinfo['present']) == 0:
                    continue

                of = open(summaryinfo['ofpath'], 'wb')

                for mdata in summaryinfo['present']:
                    rdata = None
                    if not mdata[0]:
                        continue

                    try:
                        rdata = self.kaddr_space.base.read(mdata[0], mdata[2])
                    except (IOError, OverflowError):
                        debug.debug("IOError: Pid: {0} File: {1} PhysAddr: {2} Size: {3}".format(summaryinfo['pid'], summaryinfo['name'], mdata[0], mdata[2]))

                    if not rdata:
                        continue

                    of.seek(mdata[1])
                    of.write(rdata)
                    continue

                # XXX Verify FileOffsets
                #for zpad in summaryinfo['pad']:
                #    print "ZPAD 0x%x"%(zpad[0])
                #    of.seek(zpad[0])
                #    of.write("\0" * zpad[1])

                if self._config.SUMMARY_FILE:
                    json.dump(summaryinfo, summaryfo)
                    summaryfo.write("\n")
                of.close()

            elif summaryinfo['type'] == "SharedCacheMap":

                outfd.write("SharedCacheMap {0:#010x}   {1:<6} {2}\n".format(summaryinfo['fobj'], summaryinfo['pid'], summaryinfo['name']))
                of = open(summaryinfo['ofpath'], 'wb')
                for vacb in summaryinfo['vacbary']:
                    if not vacb:
                        continue
                    (rdata, mdata, zpad) = self.audited_read_bytes(self.kaddr_space, vacb['baseaddr'], vacb['size'], True)
                    ### We need to update the mdata,zpad
                    if rdata:
                        try:
                            of.seek(vacb['foffset'])
                            of.write(rdata)
                        except IOError:
                            # TODO: Handle things like write errors (not enough disk space, etc)
                            continue
                    vacb['present'] = mdata
                    vacb['pad'] = zpad

                if self._config.SUMMARY_FILE:
                    json.dump(summaryinfo, summaryfo)
                    summaryfo.write("\n")
                of.close()

            else:
                return
        if self._config.SUMMARY_FILE:
            summaryfo.close()
