# Volatility
#
# Authors:
# Michael Cohen <scudette@users.sourceforge.net>
#
# 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
#

"""
This module implements the slow thorough process scanning

@author:       Michael Cohen
@license:      GNU General Public License 2.0 or later
@contact:      scudette@users.sourceforge.net
@organization: Volatile Systems
"""

#pylint: disable-msg=C0111

import volatility.commands as commands
import volatility.cache as cache
import volatility.utils as utils
import volatility.obj as obj
import volatility.scan as scan

class DispatchHeaderCheck(scan.ScannerCheck):
    """ A very fast check for an _EPROCESS.Pcb.Header.

    This check assumes that the type and size of
    _EPROCESS.Pcb.Header are unsigned chars, but allows their
    offsets to be determined from vtypes (so they could change
    between OS versions).
    """
    order = 10

    def __init__(self, address_space, **_kwargs):
        ## Because this checks needs to be super fast we first
        ## instantiate the _EPROCESS and work out the offsets of the
        ## type and size members. Then in the check we just read those
        ## offsets directly.
        eprocess = obj.Object("_EPROCESS", vm = address_space, offset = 0)
        self.type = eprocess.Pcb.Header.Type
        self.size = eprocess.Pcb.Header.Size
        self.buffer_size = max(self.size.obj_offset, self.type.obj_offset) + 2
        scan.ScannerCheck.__init__(self, address_space)

    def check(self, offset):
        data = self.address_space.read(offset + self.type.obj_offset, self.buffer_size)
        return data[self.type.obj_offset] == "\x03" and data[self.size.obj_offset] == "\x1b"

    def skip(self, data, offset):
        try:
            nextval = data.index("\x03", offset + 1)
            return nextval - self.type.obj_offset - offset
        except ValueError:
            ## Substring is not found - skip to the end of this data buffer
            return len(data) - offset

class CheckThreadList(scan.ScannerCheck):
    """ Checks that _EPROCESS thread list points to the kernel Address Space """
    def check(self, offset):
        eprocess = obj.Object("_EPROCESS", vm = self.address_space,
                             offset = offset)
        kernel = 0x80000000

        list_head = eprocess.ThreadListHead

        if list_head.Flink > kernel and list_head.Blink > kernel:
            return True

class CheckDTBAligned(scan.ScannerCheck):
    """ Checks that _EPROCESS.Pcb.DirectoryTableBase is aligned to 0x20 """
    def check(self, offset):
        eprocess = obj.Object("_EPROCESS", vm = self.address_space,
                             offset = offset)

        return eprocess.Pcb.DirectoryTableBase % 0x20 == 0

class CheckSynchronization(scan.ScannerCheck):
    """ Checks that _EPROCESS.WorkingSetLock and _EPROCESS.AddressCreationLock look valid """
    def check(self, offset):
        eprocess = obj.Object("_EPROCESS", vm = self.address_space,
                             offset = offset)

        event = eprocess.WorkingSetLock.Event.Header
        if event.Type != 0x1 or event.Size != 0x4:
            return False

        event = eprocess.AddressCreationLock.Event.Header
        if event.Size == 0x4 and event.Type == 0x1:
            return True

class PSDispScanner(scan.BaseScanner):
    """ This scanner carves things that look like _EPROCESS structures.

    Since the _EPROCESS does not need to be linked to the process
    list, this scanner is useful to recover terminated or cloaked
    processes.
    """
    checks = [ ("DispatchHeaderCheck", {}),
               ("CheckDTBAligned", {}),
               ("CheckThreadList", {}),
               ("CheckSynchronization", {})
               ]

class PSDispScan(commands.Command, cache.Testable):
    """ Scan Physical memory for _EPROCESS objects based on their Dispatch Headers"""

    # Declare meta information associated with this plugin

    meta_info = dict(
        author = 'Brendan Dolan-Gavitt',
        copyright = 'Copyright (c) 2007,2008 Brendan Dolan-Gavitt',
        contact = 'bdolangavitt@wesleyan.edu',
        license = 'GNU General Public License 2.0 or later',
        url = 'http://moyix.blogspot.com/',
        os = 'WIN_32_XP_SP2',
        version = '1.0',
        )

    @cache.CacheDecorator("tests/psscan")
    def calculate(self):
        address_space = utils.load_as(self._config, astype = 'physical')

        for offset in PSDispScanner().scan(address_space):
            yield obj.Object('_EPROCESS', vm = address_space, offset = offset)

    def render_dot(self, outfd, data):
        objects = set()
        links = set()

        for eprocess in data:
            label = "{0} | {1} |".format(eprocess.UniqueProcessId,
                                         eprocess.ImageFileName)
            if eprocess.ExitTime:
                label += "exited\\n{0}".format(eprocess.ExitTime)
                options = ' style = "filled" fillcolor = "lightgray" '
            else:
                label += "running"
                options = ''

            objects.add('pid{0} [label="{1}" shape="record" {2}];\n'.format(eprocess.UniqueProcessId,
                                                                            label, options))
            links.add("pid{0} -> pid{1} [];\n".format(eprocess.InheritedFromUniqueProcessId,
                                                      eprocess.UniqueProcessId))

        ## Now write the dot file
        outfd.write("digraph processtree { \ngraph [rankdir = \"TB\"];\n")
        for link in links:
            outfd.write(link)

        for item in objects:
            outfd.write(item)
        outfd.write("}")

    def render_text(self, outfd, data):
        ## Just grab the AS and scan it using our scanner
        outfd.write(" Offset     Name             PID    PPID   PDB        Time created             Time exited             \n" +
                    "---------- ---------------- ------ ------ ---------- ------------------------ ------------------------ \n")

        for eprocess in data:
            outfd.write("{0:#010x} {1:16} {2:6} {3:6} {4:#010x} {5:24} {6:24}\n".format(
                eprocess.obj_offset,
                eprocess.ImageFileName,
                eprocess.UniqueProcessId,
                eprocess.InheritedFromUniqueProcessId,
                eprocess.Pcb.DirectoryTableBase,
                eprocess.CreateTime or '',
                eprocess.ExitTime or ''))
