# Volatility
# Copyright (c) 2010, 2011, 2012 Michael Ligh <michael.ligh@mnin.org>
#
# 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 
#

import volatility.utils as utils
import volatility.obj as obj
import volatility.plugins.common as common
import volatility.win32.modules as modules
import volatility.win32.tasks as tasks
import volatility.debug as debug
import volatility.plugins.malware.malfind as malfind
import volatility.exceptions as exceptions

#--------------------------------------------------------------------------------
# constants 
#--------------------------------------------------------------------------------

GDT_DESCRIPTORS = dict(enumerate([
    "Data RO",
    "Data RO Ac",
    "Data RW",
    "Data RW Ac",
    "Data RO E",
    "Data RO EA",
    "Data RW E",
    "Data RW EA",
    "Code EO",
    "Code EO Ac",
    "Code RE",
    "Code RE Ac",
    "Code EO C",
    "Code EO CA",
    "Code RE C",
    "Code RE CA",
    "<Reserved>",
    "TSS16 Avl",
    "LDT",
    "TSS16 Busy",
    "CallGate16",
    "TaskGate",
    "Int Gate16",
    "TrapGate16",
    "<Reserved>",
    "TSS32 Avl",
    "<Reserved>",
    "TSS32 Busy",
    "CallGate32",
    "<Reserved>",
    "Int Gate32",
    "TrapGate32",
]))

#--------------------------------------------------------------------------------
# object classes  
#--------------------------------------------------------------------------------

class _KIDTENTRY(obj.CType):
    """Class for interrupt descriptors"""

    @property
    def Address(self):
        """Return the address of the IDT entry handler"""

        if self.ExtendedOffset == 0:
            return 0

        return (self.ExtendedOffset.v() << 16 | self.Offset.v())

class _KGDTENTRY(obj.CType):
    """A class for GDT entries"""

    @property
    def Type(self):
        """Get a string name of the descriptor type"""

        flag = self.HighWord.Bits.Type.v() & 1 << 4
        typeval = self.HighWord.Bits.Type.v() & ~(1 << 4)

        if flag == 0:
            typeval += 16

        return GDT_DESCRIPTORS.get(typeval, "UNKNOWN")

    @property
    def Base(self):
        """Get the base (start) of memory for this GDT"""
        return (self.BaseLow + ((self.HighWord.Bits.BaseMid +
               (self.HighWord.Bits.BaseHi << 8)) << 16))

    @property
    def Limit(self):
        """Get the limit (end) of memory for this GDT"""
        limit = (self.HighWord.Bits.LimitHi.v() << 16) | self.LimitLow.v()

        if self.HighWord.Bits.Granularity == 1:
            limit = (limit + 1) * 0x1000
            limit -= 1

        return limit

    @property
    def CallGate(self):
        """Get the call gate address"""
        return self.HighWord.v() & 0xffff0000 | self.LimitLow.v()

    @property
    def Present(self):
        """Returns True if the entry is present"""
        return self.HighWord.Bits.Pres == 1

    @property
    def Granularity(self):
        """Returns True if page granularity is used. Otherwise
        returns False indicating byte granularity is used."""
        return self.HighWord.Bits.Granularity == 1

    @property
    def Dpl(self):
        """Returns the descriptor privilege level"""
        return self.HighWord.Bits.Dpl

#--------------------------------------------------------------------------------
# profile modifications 
#--------------------------------------------------------------------------------

class MalwareIDTGDTx86(obj.ProfileModification):
    before = ['WindowsObjectClasses', 'WindowsOverlay']
    conditions = {'os': lambda x: x == 'windows',
                  'memory_model': lambda x: x == '32bit'}
    def modification(self, profile):
        profile.object_classes.update({
            '_KIDTENTRY': _KIDTENTRY,
            '_KGDTENTRY': _KGDTENTRY,
        })
        profile.merge_overlay({"_KPCR" : [None,
            {'IDT': [None,
                    ["pointer", ["array", 256, ['_KIDTENTRY']]]],
            }]})
        # Since the real GDT size is read from a register, we'll just assume
        # that there are 128 entries (which is normal for most OS)
        profile.merge_overlay({"_KPCR" : [None,
            {'GDT': [None,
                    ["pointer", ["array", 128, ['_KGDTENTRY']]]],
            }]})

#--------------------------------------------------------------------------------
# GDT plugin 
#--------------------------------------------------------------------------------

class GDT(common.AbstractWindowsCommand):
    "Display Global Descriptor Table"

    @staticmethod
    def is_valid_profile(profile):
        return (profile.metadata.get('os', 'unknown') == 'windows' and
                profile.metadata.get('memory_model', '32bit') == '32bit')

    def calculate(self):
        addr_space = utils.load_as(self._config)

        # Currently we only support x86. The x64 does still have a GDT 
        # but hooking is prohibited and results in bugcheck. 
        if not self.is_valid_profile(addr_space.profile):
            debug.error("This command does not support the selected profile.")

        for kpcr in tasks.get_kdbg(addr_space).kpcrs():
            for i, entry in kpcr.gdt_entries():
                yield i, entry

    def render_text(self, outfd, data):

        self.table_header(outfd, [('CPU', '>6'),
                                  ('Sel', '[addr]'),
                                  ('Base', '[addrpad]'),
                                  ('Limit', '[addrpad]'),
                                  ('Type', '<14'),
                                  ('DPL', '>6'),
                                  ('Gr', '<4'),
                                  ('Pr', '<4')
                                  ])

        for n, entry in data:

            selector = n * 8

            # Is the entry present? This applies to all types of GDT entries
            if entry.Present:
                present = "P"
            else:
                present = "Np"

            # The base, limit, and granularity is calculated differently
            # for 32bit call gates than they are for all other types. 
            if entry.Type == 'CallGate32':
                base = entry.CallGate
                limit = 0
                granularity = '-'
            else:
                base = entry.Base
                limit = entry.Limit
                if entry.Granularity:
                    granularity = "Pg"
                else:
                    granularity = "By"

            # The parent is GDT. The grand-parent is _KPCR
            cpu_number = entry.obj_parent.obj_parent.ProcessorBlock.Number

            self.table_row(outfd,
                           cpu_number,
                           selector,
                           base, limit,
                           entry.Type,
                           entry.Dpl,
                           granularity,
                           present)

#--------------------------------------------------------------------------------
# IDT plugin 
#--------------------------------------------------------------------------------

class IDT(common.AbstractWindowsCommand):
    "Display Interrupt Descriptor Table"

    @staticmethod
    def is_valid_profile(profile):
        return (profile.metadata.get('os', 'unknown') == 'windows' and
                profile.metadata.get('memory_model', '32bit') == '32bit')

    @staticmethod
    def get_section_name(mod, addr):
        """Get the name of the PE section containing 
        the specified address. 

        @param mod: an _LDR_DATA_TABLE_ENTRY 
        @param addr: virtual address to lookup 
        
        @returns string PE section name
        """

        try:
            dos_header = obj.Object("_IMAGE_DOS_HEADER",
                            offset = mod.DllBase, vm = mod.obj_vm)
            nt_header = dos_header.get_nt_header()
        except (ValueError, exceptions.SanityCheckException):
            return ''

        for sec in nt_header.get_sections(False):
            if (addr > mod.DllBase + sec.VirtualAddress and
                    addr < sec.Misc.VirtualSize + (mod.DllBase + sec.VirtualAddress)):
                return str(sec.Name or '')

        return ''

    def calculate(self):
        addr_space = utils.load_as(self._config)

        # Currently we only support x86. The x64 does still have a IDT 
        # but hooking is prohibited and results in bugcheck. 
        if not self.is_valid_profile(addr_space.profile):
            debug.error("This command does not support the selected profile.")

        mods = dict((mod.DllBase, mod) for mod in modules.lsmod(addr_space))
        mod_addrs = sorted(mods.keys())

        for kpcr in tasks.get_kdbg(addr_space).kpcrs():
            # Get the GDT for access to selector bases
            gdt = dict((i * 8, sd.Base) for i, sd in kpcr.gdt_entries())
            for i, entry in kpcr.idt_entries():
                # Where the IDT entry points. Per MITRE, add the GDT selector 
                # base if available. This allows us to detect sneaky attempts 
                # to hook IDT entries by changing the entry's GDT selector. 
                addr = entry.Address + gdt.get(entry.Selector.v(), 0)

                # Lookup the function's owner 
                module = tasks.find_module(mods, mod_addrs, addr)

                yield i, entry, addr, module

    def render_text(self, outfd, data):

        self.table_header(outfd, [('CPU', '>6X'),
                                  ('Index', '>6X'),
                                  ('Selector', '>6'),
                                  ('Value', '[addrpad]'),
                                  ('Module', '20'),
                                  ('Section', '12'),
                                  ])

        for n, entry, _addr, module in data:

            if module:
                module_name = str(module.BaseDllName or '')
                sect_name = self.get_section_name(module, entry.Address)
            else:
                module_name = "UNKNOWN"
                sect_name = ''

            # The parent is IDT. The grand-parent is _KPCR. 
            cpu_number = entry.obj_parent.obj_parent.ProcessorBlock.Number

            self.table_row(outfd,
                          cpu_number, n,
                          entry.Selector,
                          entry.Address,
                          module_name,
                          sect_name)

            if self._config.verbose:
                data = entry.obj_vm.zread(entry.Address, 32)
                outfd.write("\n".join(
                    ["{0:#x} {1:<16} {2}".format(o, h, i)
                    for o, i, h in malfind.Disassemble(data = data,
                        start = entry.Address, stoponret = True)
                ]))
                outfd.write("\n")
