# Volatility
# Copyright (C) 2007-2013 Volatility Foundation
#
# Authors:
# Michael Hale Ligh <michael.ligh@mnin.org>
#
# Contributors/References:
#   Richard Stevens and Eoghan Casey
#   Extracting Windows Cmd Line Details from Physical Memory.
#   http://ww.dfrws.org/2010/proceedings/stevens.pdf
#
# 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/>.
#

import volatility.obj as obj
import volatility.plugins.common as common
import volatility.utils as utils
import volatility.win32.tasks as tasks
import volatility.debug as debug

MAX_HISTORY_DEFAULT = 50

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

# Windows 7 Types from conhost.exe
conhost_types_x86 = {
    '_COMMAND': [ None, {
    'CmdLength': [ 0x00, ['unsigned short']],
    'Cmd' : [ 0x02, ['String', dict(encoding = 'utf16', length = lambda x : x.CmdLength)]],
    }],
    '_COMMAND_HISTORY': [ None, {
    'ListEntry': [ 0x00, ['_LIST_ENTRY']],
    'Flags' : [ 0x08, ['Flags', {'bitmap': {'Allocated': 0, 'Reset': 1}}]],
    'Application': [ 0x0C, ['pointer', ['String', dict(encoding = 'utf16', length = 256)]]],
    'CommandCount': [ 0x10, ['short']],
    'LastAdded': [ 0x12, ['short']],
    'LastDisplayed': [ 0x14, ['short']],
    'FirstCommand': [ 0x16, ['short']],
    'CommandCountMax': [ 0x18, ['short']],
    'ProcessHandle': [ 0x1C, ['unsigned int']],
    'PopupList': [ 0x20, ['_LIST_ENTRY']],
    'CommandBucket': [ 0x28, ['array', lambda x : x.CommandCount, ['pointer', ['_COMMAND']]]],
    }],
    '_ALIAS': [ None, {
    'ListEntry': [ 0x00, ['_LIST_ENTRY']],
    'SourceLength': [ 0x08, ['unsigned short']],
    'TargetLength': [ 0x0A, ['unsigned short']],
    'Source': [ 0x0C, ['pointer', ['String', dict(encoding = 'utf16', length = lambda x : x.SourceLength)]]],
    'Target': [ 0x10, ['pointer', ['String', dict(encoding = 'utf16', length = lambda x : x.TargetLength)]]],
    }],
    '_EXE_ALIAS_LIST' : [ None, {
    'ListEntry': [ 0x00, ['_LIST_ENTRY']],
    'ExeLength': [ 0x08, ['unsigned short']],
    'ExeName': [ 0x0C, ['pointer', ['String', dict(encoding = 'utf16', length = lambda x : x.ExeLength * 2)]]],
    'AliasList': [ 0x10, ['_LIST_ENTRY']],
    }],
    '_POPUP_LIST' : [ None, {
    'ListEntry' : [ 0x00, ['_LIST_ENTRY']],
    }],
    '_CONSOLE_INFORMATION': [ None, {
    'CurrentScreenBuffer': [ 0x98, ['pointer', ['_SCREEN_INFORMATION']]],
    'ScreenBuffer': [ 0x9C, ['pointer', ['_SCREEN_INFORMATION']]],
    'HistoryList': [ 0xD4, ['_LIST_ENTRY']],
    'ProcessList': [ 0x18, ['_LIST_ENTRY']], # GetConsoleProcessList()
    'ExeAliasList': [ 0xDC, ['_LIST_ENTRY']], # GetConsoleAliasExes() 
    'HistoryBufferCount': [ 0xE4, ['unsigned short']], # GetConsoleHistoryInfo()
    'HistoryBufferMax': [ 0xE6, ['unsigned short']], # GetConsoleHistoryInfo()
    'CommandHistorySize': [ 0xE8, ['unsigned short']],
    'OriginalTitle': [ 0xEC, ['pointer', ['String', dict(encoding = 'utf16', length = 256)]]], # GetConsoleOriginalTitle()
    'Title': [ 0xF0, ['pointer', ['String', dict(encoding = 'utf16', length = 256)]]], # GetConsoleTitle()
    }],
    '_CONSOLE_PROCESS': [ None, {
    'ListEntry': [ 0x00, ['_LIST_ENTRY']],
    'ProcessHandle': [ 0x8, ['unsigned int']],
    }],
    '_SCREEN_INFORMATION': [ None, {
    'ScreenX': [ 0x08, ['short']],
    'ScreenY': [ 0x0A, ['short']],
    'Rows': [ 0x3C, ['pointer', ['array', lambda x : x.ScreenY, ['_ROW']]]],
    'Next': [ 0xDC, ['pointer', ['_SCREEN_INFORMATION']]],
    }],
    '_ROW': [ 0x1C, {
    'Chars': [ 0x08, ['pointer', ['String', dict(encoding = 'utf16', length = 256)]]],
    }],
}

# Windows 7 Types from conhost.exe
conhost_types_x64 = {
    '_COMMAND': [ None, {
    'CmdLength': [ 0x00, ['unsigned short']],
    'Cmd' : [ 0x02, ['String', dict(encoding = 'utf16', length = lambda x : x.CmdLength)]],
    }],
    '_COMMAND_HISTORY': [ None, {
    'ListEntry': [ 0x00, ['_LIST_ENTRY']], 
    'Flags' : [ 0x10, ['Flags', {'bitmap': {'Allocated': 0, 'Reset': 1}}]], # AllocateCommandHistory()
    'Application': [ 0x18, ['pointer', ['String', dict(encoding = 'utf16', length = 256)]]], # AllocateCommandHistory()
    'CommandCount': [ 0x20, ['short']], 
    'LastAdded': [ 0x22, ['short']], 
    'LastDisplayed': [ 0x24, ['short']],
    'FirstCommand': [ 0x26, ['short']], 
    'CommandCountMax': [ 0x28, ['short']], # AllocateCommandHistory()
    'ProcessHandle': [ 0x30, ['address']], # AllocateCommandHistory()
    'PopupList': [ 0x38, ['_LIST_ENTRY']], # AllocateCommandHistory()
    'CommandBucket': [ 0x48, ['array', lambda x : x.CommandCount, ['pointer', ['_COMMAND']]]], 
    }],
    '_ALIAS': [ None, {
    'ListEntry': [ 0x00, ['_LIST_ENTRY']], 
    'SourceLength': [ 0x10, ['unsigned short']], # AddAlias()
    'TargetLength': [ 0x12, ['unsigned short']], # AddAlias()
    'Source': [ 0x18, ['pointer', ['String', dict(encoding = 'utf16', length = lambda x : x.SourceLength)]]], # AddAlias()
    'Target': [ 0x20, ['pointer', ['String', dict(encoding = 'utf16', length = lambda x : x.TargetLength)]]], # AddAlias()
    }],
    '_EXE_ALIAS_LIST' : [ None, {
    'ListEntry': [ 0x00, ['_LIST_ENTRY']], 
    'ExeLength': [ 0x10, ['unsigned short']], # AddExeAliasList()
    'ExeName': [ 0x18, ['pointer', ['String', dict(encoding = 'utf16', length = lambda x : x.ExeLength * 2)]]], # AddExeAliasList()
    'AliasList': [ 0x20, ['_LIST_ENTRY']], # AddExeAliasList()
    }],
    '_POPUP_LIST' : [ None, {
    'ListEntry' : [ 0x00, ['_LIST_ENTRY']],
    }],
    '_CONSOLE_INFORMATION': [ None, {
    'ProcessList': [ 0x28, ['_LIST_ENTRY']], # SrvGetConsoleProcessList()
    'CurrentScreenBuffer': [ 0xE0, ['pointer', ['_SCREEN_INFORMATION']]], # AllocateConsole()
    'ScreenBuffer': [ 0xE8, ['pointer', ['_SCREEN_INFORMATION']]], # AllocateConsole()
    'HistoryList': [ 0x148, ['_LIST_ENTRY']], # AllocateCommandHistory()
    'ExeAliasList': [ 0x158, ['_LIST_ENTRY']], # SrvGetConsoleAliasExes()
    'HistoryBufferCount': [ 0x168, ['unsigned short']], # AllocateConsole()
    'HistoryBufferMax': [ 0x16A, ['unsigned short']], # AllocateConsole()
    'CommandHistorySize': [ 0x16C, ['unsigned short']], # AllocateConsole()
    'OriginalTitle': [ 0x170, ['pointer', ['String', dict(encoding = 'utf16', length = 256)]]], # SrvGetConsoleTitle()
    'Title': [ 0x178, ['pointer', ['String', dict(encoding = 'utf16', length = 256)]]], # SrvGetConsoleTitle()
    }],
    '_CONSOLE_PROCESS': [ None, {
    'ListEntry': [ 0x00, ['_LIST_ENTRY']],
    'ProcessHandle': [ 0x10, ['unsigned int']], # FindProcessInList()
    }],
    '_SCREEN_INFORMATION': [ None, {
    'ScreenX': [ 8, ['short']], 
    'ScreenY': [ 10, ['short']], 
    'Rows': [ 0x48, ['pointer', ['array', lambda x : x.ScreenY, ['_ROW']]]], 
    'Next': [ 0x128, ['pointer', ['_SCREEN_INFORMATION']]],
    }],
    '_ROW': [ 0x28, { 
    'Chars': [ 0x08, ['pointer', ['String', dict(encoding = 'utf16', length = 256)]]], 
    }],
}

# Windows XP, 2003, 2008, Vista from winsrv.dll
winsrv_types_x86 = {
    '_COMMAND': [ None, {
    'CmdLength': [ 0x00, ['unsigned short']],
    'Cmd' : [ 0x02, ['String', dict(encoding = 'utf16', length = lambda x : x.CmdLength)]],
    }],
    '_COMMAND_HISTORY': [ None, {
    'Flags' : [ 0x00, ['Flags', {'bitmap': {'Allocated': 0, 'Reset': 1}}]],
    'ListEntry': [ 0x04, ['_LIST_ENTRY']],
    'Application': [ 0x0C, ['pointer', ['String', dict(encoding = 'utf16', length = 256)]]],
    'CommandCount': [ 0x10, ['short']],
    'LastAdded': [ 0x12, ['short']],
    'LastDisplayed': [ 0x14, ['short']],
    'FirstCommand': [ 0x16, ['short']],
    'CommandCountMax': [ 0x18, ['short']],
    'ProcessHandle': [ 0x1C, ['unsigned int']],
    'PopupList': [ 0x20, ['_LIST_ENTRY']],
    'CommandBucket': [ 0x28, ['array', lambda x : x.CommandCount, ['pointer', ['_COMMAND']]]],
    }],
    '_ALIAS': [ None, {
    'ListEntry': [ 0x00, ['_LIST_ENTRY']],
    'SourceLength': [ 0x08, ['unsigned short']],
    'TargetLength': [ 0x0A, ['unsigned short']],
    'Source': [ 0x0C, ['pointer', ['String', dict(encoding = 'utf16', length = lambda x : x.SourceLength)]]],
    'Target': [ 0x10, ['pointer', ['String', dict(encoding = 'utf16', length = lambda x : x.TargetLength)]]],
    }],
    '_EXE_ALIAS_LIST' : [ None, {
    'ListEntry': [ 0x00, ['_LIST_ENTRY']],
    'ExeLength': [ 0x08, ['unsigned short']],
    'ExeName': [ 0x0C, ['pointer', ['String', dict(encoding = 'utf16', length = lambda x : x.ExeLength * 2)]]],
    'AliasList': [ 0x10, ['_LIST_ENTRY']],
    }],
    '_POPUP_LIST' : [ None, {
    'ListEntry' : [ 0x00, ['_LIST_ENTRY']],
    }],
    '_CONSOLE_INFORMATION': [ None, {
    'CurrentScreenBuffer': [ 0xB0, ['pointer', ['_SCREEN_INFORMATION']]],
    'ScreenBuffer': [ 0xB4, ['pointer', ['_SCREEN_INFORMATION']]],
    'HistoryList': [ 0x108, ['_LIST_ENTRY']],
    'ProcessList': [ 0x100, ['_LIST_ENTRY']],
    'ExeAliasList': [ 0x110, ['_LIST_ENTRY']],
    'HistoryBufferCount': [ 0x118, ['unsigned short']],
    'HistoryBufferMax': [ 0x11A, ['unsigned short']],
    'CommandHistorySize': [ 0x11C, ['unsigned short']],
    'OriginalTitle': [ 0x124, ['pointer', ['String', dict(encoding = 'utf16', length = 256)]]],
    'Title': [ 0x128, ['pointer', ['String', dict(encoding = 'utf16', length = 256)]]],
    }],
    '_CONSOLE_PROCESS': [ None, {
    'ListEntry': [ 0x00, ['_LIST_ENTRY']],
    'ProcessHandle': [ 0x08, ['unsigned int']],
    'Process': [ 0x0C, ['pointer', ['_CSR_PROCESS']]],
    }],
    '_SCREEN_INFORMATION': [ None, {
    'Console': [ 0x00, ['pointer', ['_CONSOLE_INFORMATION']]],
    'ScreenX': [ 0x24, ['short']],
    'ScreenY': [ 0x26, ['short']],
    'Rows': [ 0x58, ['pointer', ['array', lambda x : x.ScreenY, ['_ROW']]]],
    'Next': [ 0xF8, ['pointer', ['_SCREEN_INFORMATION']]],
    }],
    '_ROW': [ 0x1C, {
    'Chars': [ 0x08, ['pointer', ['String', dict(encoding = 'utf16', length = 256)]]],
    }],
    '_CSR_PROCESS' : [ 0x60, { # this is a public PDB  
    'ClientId' : [ 0x0, ['_CLIENT_ID']],
    'ListLink' : [ 0x8, ['_LIST_ENTRY']],
    'ThreadList' : [ 0x10, ['_LIST_ENTRY']],
    'NtSession' : [ 0x18, ['pointer', ['_CSR_NT_SESSION']]],
    'ClientPort' : [ 0x1c, ['pointer', ['void']]],
    'ClientViewBase' : [ 0x20, ['pointer', ['unsigned char']]],
    'ClientViewBounds' : [ 0x24, ['pointer', ['unsigned char']]],
    'ProcessHandle' : [ 0x28, ['pointer', ['void']]],
    'SequenceNumber' : [ 0x2c, ['unsigned long']],
    'Flags' : [ 0x30, ['unsigned long']],
    'DebugFlags' : [ 0x34, ['unsigned long']],
    'ReferenceCount' : [ 0x38, ['unsigned long']],
    'ProcessGroupId' : [ 0x3c, ['unsigned long']],
    'ProcessGroupSequence' : [ 0x40, ['unsigned long']],
    'LastMessageSequence' : [ 0x44, ['unsigned long']],
    'NumOutstandingMessages' : [ 0x48, ['unsigned long']],
    'ShutdownLevel' : [ 0x4c, ['unsigned long']],
    'ShutdownFlags' : [ 0x50, ['unsigned long']],
    'Luid' : [ 0x54, ['_LUID']],
    'ServerDllPerProcessData' : [ 0x5c, ['array', 1, ['pointer', ['void']]]],
    }],
}

winsrv_types_x64 = {
    '_COMMAND': [ None, {
    'CmdLength': [ 0x00, ['unsigned short']],
    'Cmd' : [ 0x02, ['String', dict(encoding = 'utf16', length = lambda x : x.CmdLength)]],
    }],
    '_COMMAND_HISTORY': [ None, {
    'Flags' : [ 0x00, ['Flags', {'bitmap': {'Allocated': 0, 'Reset': 1}}]],
    'ListEntry': [ 0x08, ['_LIST_ENTRY']],
    'Application': [ 0x18, ['pointer', ['String', dict(encoding = 'utf16', length = 256)]]],
    'CommandCount': [ 0x20, ['short']],
    'LastAdded': [ 0x22, ['short']],
    'LastDisplayed': [ 0x24, ['short']],
    'FirstCommand': [ 0x26, ['short']],
    'CommandCountMax': [ 0x28, ['short']],
    'ProcessHandle': [ 0x30, ['unsigned int']],
    'PopupList': [ 0x38, ['_LIST_ENTRY']],
    'CommandBucket': [ 0x48, ['array', lambda x : x.CommandCount, ['pointer', ['_COMMAND']]]],
    }],
    '_ALIAS': [ None, {
    'ListEntry': [ 0x00, ['_LIST_ENTRY']],
    'SourceLength': [ 0x10, ['unsigned short']],
    'TargetLength': [ 0x12, ['unsigned short']],
    'Source': [ 0x14, ['pointer', ['String', dict(encoding = 'utf16', length = lambda x : x.SourceLength)]]],
    'Target': [ 0x1C, ['pointer', ['String', dict(encoding = 'utf16', length = lambda x : x.TargetLength)]]],
    }],
    '_EXE_ALIAS_LIST' : [ None, {
    'ListEntry': [ 0x00, ['_LIST_ENTRY']],
    'ExeLength': [ 0x10, ['unsigned short']],
    'ExeName': [ 0x12, ['pointer', ['String', dict(encoding = 'utf16', length = lambda x : x.ExeLength * 2)]]],
    'AliasList': [ 0x1A, ['_LIST_ENTRY']],
    }],
    '_POPUP_LIST' : [ None, {
    'ListEntry' : [ 0x00, ['_LIST_ENTRY']],
    }],
    '_CONSOLE_INFORMATION': [ None, {
    'CurrentScreenBuffer': [ 0xE8, ['pointer', ['_SCREEN_INFORMATION']]],
    'ScreenBuffer': [ 0xF0, ['pointer', ['_SCREEN_INFORMATION']]],
    'HistoryList': [ 0x188, ['_LIST_ENTRY']],
    'ProcessList': [ 0x178, ['_LIST_ENTRY']],
    'ExeAliasList': [ 0x198, ['_LIST_ENTRY']],
    'HistoryBufferCount': [ 0x1A8, ['unsigned short']],
    'HistoryBufferMax': [ 0x1AA, ['unsigned short']],
    'CommandHistorySize': [ 0x1AC, ['unsigned short']],
    'OriginalTitle': [ 0x1B0, ['pointer', ['String', dict(encoding = 'utf16', length = 256)]]],
    'Title': [ 0x1B8, ['pointer', ['String', dict(encoding = 'utf16', length = 256)]]],
    }],
    '_CONSOLE_PROCESS': [ None, {
    'ListEntry': [ 0x00, ['_LIST_ENTRY']],
    'ProcessHandle': [ 0x10, ['unsigned int']],
    'Process': [ 0x18, ['pointer', ['_CSR_PROCESS']]],
    }],
    '_SCREEN_INFORMATION': [ None, {
    'Console': [ 0x00, ['pointer', ['_CONSOLE_INFORMATION']]],
    'ScreenX': [ 0x28, ['short']],
    'ScreenY': [ 0x2A, ['short']],
    'Rows': [ 0x68, ['pointer', ['array', lambda x : x.ScreenY, ['_ROW']]]],
    'Next': [ 0x128, ['pointer', ['_SCREEN_INFORMATION']]],
    }],
    '_ROW': [ 0x28, {
    'Chars': [ 0x08, ['pointer', ['String', dict(encoding = 'utf16', length = 256)]]],
    }],
    '_CSR_PROCESS' : [ 0x60, { # this is a public PDB  
    'ClientId' : [ 0x0, ['_CLIENT_ID']],
    'ListLink' : [ 0x8, ['_LIST_ENTRY']],
    'ThreadList' : [ 0x10, ['_LIST_ENTRY']],
    'NtSession' : [ 0x18, ['pointer', ['_CSR_NT_SESSION']]],
    'ClientPort' : [ 0x1c, ['pointer', ['void']]],
    'ClientViewBase' : [ 0x20, ['pointer', ['unsigned char']]],
    'ClientViewBounds' : [ 0x24, ['pointer', ['unsigned char']]],
    'ProcessHandle' : [ 0x28, ['pointer', ['void']]],
    'SequenceNumber' : [ 0x2c, ['unsigned long']],
    'Flags' : [ 0x30, ['unsigned long']],
    'DebugFlags' : [ 0x34, ['unsigned long']],
    'ReferenceCount' : [ 0x38, ['unsigned long']],
    'ProcessGroupId' : [ 0x3c, ['unsigned long']],
    'ProcessGroupSequence' : [ 0x40, ['unsigned long']],
    'LastMessageSequence' : [ 0x44, ['unsigned long']],
    'NumOutstandingMessages' : [ 0x48, ['unsigned long']],
    'ShutdownLevel' : [ 0x4c, ['unsigned long']],
    'ShutdownFlags' : [ 0x50, ['unsigned long']],
    'Luid' : [ 0x54, ['_LUID']],
    'ServerDllPerProcessData' : [ 0x5c, ['array', 1, ['pointer', ['void']]]],
    }],
}

#--------------------------------------------------------------------------------
# Object Classes 
#--------------------------------------------------------------------------------

class _CONSOLE_INFORMATION(obj.CType):
    """ object class for console information structs """

    def get_histories(self):
        for hist in self.HistoryList.list_of_type("_COMMAND_HISTORY", "ListEntry"):
            yield hist

    def get_exe_aliases(self):
        """Generator for exe aliases.

        There is one _EXE_ALIAS_LIST for each executable 
        (i.e. C:\windows\system32\cmd.exe) with registered
        aliases. The _EXE_ALIAS_LIST.AliasList contains 
        one _ALIAS structure for each specific mapping.

        See GetConsoleAliasExes, GetConsoleAliases, and  
        AddConsoleAlias. 
        """
        for exe_alias in self.ExeAliasList.list_of_type("_EXE_ALIAS_LIST", "ListEntry"):
            yield exe_alias

    def get_processes(self):
        """Generator for processes attached to the console. 

        Multiple processes can be attached to the same
        console (usually as a result of inheritance from a 
        parent process or by duplicating another process's 
        console handle). Internally, they are tracked as 
        _CONSOLE_PROCESS structures in this linked list. 

        See GetConsoleProcessList and AttachConsole. 
        """
        for h in self.ProcessList.list_of_type("_CONSOLE_PROCESS", "ListEntry"):
            yield h

    def get_screens(self):
        """Generator for screens in the console. 

        A console can have multiple screen buffers at a time, 
        but only the current/active one is displayed. 

        Multiple screens are tracked using the singly-linked
        list _SCREEN_INFORMATION.Next. 
    
        See CreateConsoleScreenBuffer 
        """
        screens = [self.CurrentScreenBuffer]

        if self.ScreenBuffer not in screens:
            screens.append(self.ScreenBuffer)

        for screen in screens:
            cur = screen
            while cur and cur.v() != 0:
                yield cur
                cur = cur.Next.dereference()

class _CONSOLE_PROCESS(obj.CType):
    """ object class for console process """

    def reference_object_by_handle(self):
        """ Given a process handle, return a reference to 
        the _EPROCESS object. This function is similar to 
        the kernel API ObReferenceObjectByHandle. """

        console_information = self.obj_parent
        parent_process = console_information.obj_parent

        for h in parent_process.ObjectTable.handles():
            if h.HandleValue == self.ProcessHandle:
                return h.dereference_as("_EPROCESS")

        return obj.NoneObject("Could not find process in handle table")

class _SCREEN_INFORMATION(obj.CType):
    """ object class for screen information """

    def get_buffer(self, truncate = True):
        """Get the screen buffer. 

        The screen buffer is comprised of the screen's Y 
        coordinate which tells us the number of rows and 
        the X coordinate which tells us the width of each
        row in characters. These together provide all of 
        the input and output that users see when the 
        console is displayed. 

        @param truncate: True if the empty rows at the 
        end (i.e. bottom) of the screen buffer should be 
        supressed.
        """
        rows = []

        for _, row in enumerate(self.Rows.dereference()):
            if row.Chars.is_valid():
                rows.append(str(row.Chars.dereference())[0:self.ScreenX])

        # To truncate empty rows at the end, walk the list
        # backwards and get the last non-empty row. Use that
        # row index to splice. An "empty" row isn't just "" 
        # as one might assume. It is actually ScreenX number 
        # of space characters

        if truncate:
            non_empty_index = 0
            for index, row in enumerate(reversed(rows)):
                ## It seems that when the buffer width is greater than 128 
                ## characters, its truncated to 128 in memory. 
                if row.count(" ") != min(self.ScreenX, 128):
                    non_empty_index = index
                    break
            if non_empty_index == 0:
                rows = []
            else:
                rows = rows[0:len(rows) - non_empty_index]

        return rows

class _EXE_ALIAS_LIST(obj.CType):
    """ object class for alias lists """

    def get_aliases(self):
        """Generator for the individual aliases for a
        particular executable."""
        for alias in self.AliasList.list_of_type("_ALIAS", "ListEntry"):
            yield alias

class _COMMAND_HISTORY(obj.CType):
    """ object class for command histories """

    def is_valid(self, max_history = MAX_HISTORY_DEFAULT): #pylint: disable-msg=W0221
        """Override BaseObject.is_valid with some additional
        checks specific to _COMMAND_HISTORY objects."""

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

        # The count must be between zero and max 
        if self.CommandCount < 0 or self.CommandCount > max_history:
            return False

        # Last added must be between -1 and max 
        if self.LastAdded < -1 or self.LastAdded > max_history:
            return False

        # Last displayed must be between -1 and max
        if self.LastDisplayed < -1 or self.LastDisplayed > max_history:
            return False

        # First command must be between zero and max 
        if self.FirstCommand < 0 or self.FirstCommand > max_history:
            return False

        # Validate first command with last added 
        if self.FirstCommand != 0 and self.FirstCommand != self.LastAdded + 1:
            return False

        # Process handle must be a valid pid 
        if self.ProcessHandle <= 0 or self.ProcessHandle > 0xFFFF:
            return False

        Popup = obj.Object("_POPUP_LIST", offset = self.PopupList.Flink,
            vm = self.obj_vm)

        # Check that the popup list entry is in tact
        if Popup.ListEntry.Blink != self.PopupList.obj_offset:
            return False

        return True

    def get_commands(self):
        """Generator for commands in the history buffer. 

        The CommandBucket is an array of pointers to _COMMAND 
        structures. The array size is CommandCount. Once CommandCount 
        is reached, the oldest commands are cycled out and the 
        rest are coalesced. 
        """
        for i, cmd in enumerate(self.CommandBucket):
            if cmd:
                yield i, cmd.dereference()

#--------------------------------------------------------------------------------
# Profile Modifications 
#--------------------------------------------------------------------------------

class CmdHistoryVTypesx86(obj.ProfileModification):
    """This modification applies the vtypes for 32bit 
    Windows up to Windows 7."""

    before = ['WindowsObjectClasses']

    def check(self, profile):
        m = profile.metadata
        return (m.get('os', None) == 'windows' and
                m.get('memory_model', '32bit') == '32bit' and
                (m.get('major') < 6 or (m.get('major') == 6 and m.get('minor') < 1)))

    def modification(self, profile):
        profile.vtypes.update(winsrv_types_x86)

class CmdHistoryVTypesx64(obj.ProfileModification):
    """This modification applies the vtypes for 64bit 
    Windows up to Windows 7."""

    before = ['WindowsObjectClasses']

    def check(self, profile):
        m = profile.metadata
        return (m.get('os', None) == 'windows' and
                m.get('memory_model', '32bit') == '64bit' and
                (m.get('major') < 6 or (m.get('major') == 6 and m.get('minor') < 1)))

    def modification(self, profile):
        profile.vtypes.update(winsrv_types_x64)

class CmdHistoryVTypesWin7x86(obj.ProfileModification):
    """This modification applies the vtypes for 32bit 
    Windows starting with Windows 7."""

    before = ['WindowsObjectClasses']
    conditions = {'os': lambda x: x == 'windows',
                  'major': lambda x: x == 6,
                  'minor': lambda x: x >= 1,
                  'memory_model': lambda x : x == '32bit'}
    def modification(self, profile):
        profile.vtypes.update(conhost_types_x86)

class CmdHistoryVTypesWin7x64(obj.ProfileModification):
    """This modification applies the vtypes for 64bit 
    Windows starting with Windows 7."""

    before = ['WindowsObjectClasses']
    conditions = {'os': lambda x: x == 'windows',
                  'major': lambda x: x == 6,
                  'minor': lambda x: x >= 1,
                  'memory_model': lambda x : x == '64bit'}
    def modification(self, profile):
        profile.vtypes.update(conhost_types_x64)

class CmdHistoryObjectClasses(obj.ProfileModification):
    """This modification applies the object classes 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.object_classes.update({
            '_CONSOLE_INFORMATION': _CONSOLE_INFORMATION,
            '_SCREEN_INFORMATION': _SCREEN_INFORMATION,
            '_EXE_ALIAS_LIST': _EXE_ALIAS_LIST,
            '_COMMAND_HISTORY': _COMMAND_HISTORY,
            '_CONSOLE_PROCESS': _CONSOLE_PROCESS,
        })

#--------------------------------------------------------------------------------
# CmdScan Plugin 
#--------------------------------------------------------------------------------

class CmdScan(common.AbstractWindowsCommand):
    """Extract command history by scanning for _COMMAND_HISTORY"""

    def __init__(self, config, *args, **kwargs):
        common.AbstractWindowsCommand.__init__(self, config, *args, **kwargs)
        # The default comes from HKCU\Console\HistoryBufferSize
        config.add_option('MAX_HISTORY', short_option = 'M', default = MAX_HISTORY_DEFAULT,
                            action = 'store', type = 'int',
                            help = 'CommandCountMax (default = 50)')

    def cmdhistory_process_filter(self, addr_space):
        """Generator for processes that might contain command 
        history information. 

        Takes into account if we're on Windows 7 or an earlier
        operator system. 

        @param addr_space: a kernel address space. 
        """

        # Detect if we're on windows seven 
        use_conhost = (6, 1) <= (addr_space.profile.metadata.get('major', 0),
                                addr_space.profile.metadata.get('minor', 0))

        for task in tasks.pslist(addr_space):
            process_name = str(task.ImageFileName).lower()
            # The process we select is conhost on Win7 or csrss for others
            if ((use_conhost and process_name == "conhost.exe") or
                        (not use_conhost and process_name == "csrss.exe")):
                yield task

    def calculate(self):
        """The default pattern we search for, as described by Stevens and Casey, 
        is "\x32\x00". That's because CommandCountMax is a little-endian 
        unsigned short whose default value is 50. However, that value can be 
        changed by right clicking cmd.exe and going to Properties->Options->Cmd History 
        or by calling the API function kernel32!SetConsoleHistoryInfo. Thus 
        you can tweak the search criteria by using the --MAX_HISTORY. 
        """

        addr_space = utils.load_as(self._config)

        MAX_HISTORY = self._config.MAX_HISTORY
        srch_pattern = chr(MAX_HISTORY) + "\x00"

        for task in self.cmdhistory_process_filter(addr_space):
            process_space = task.get_process_address_space()
            for found in task.search_process_memory([srch_pattern]):

                hist = obj.Object("_COMMAND_HISTORY",
                        vm = process_space,
                        offset = found - addr_space.profile.\
                        get_obj_offset("_COMMAND_HISTORY", "CommandCountMax"))

                if hist.is_valid(max_history = MAX_HISTORY):
                    yield task, hist

    def render_text(self, outfd, data):

        for task, hist in data:

            outfd.write("*" * 50 + "\n")
            outfd.write("CommandProcess: {0} Pid: {1}\n".format(
                task.ImageFileName, task.UniqueProcessId))
            outfd.write("CommandHistory: {0:#x} Application: {1} Flags: {2}\n".format(
                hist.obj_offset, hist.Application.dereference(),
                hist.Flags))
            outfd.write("CommandCount: {0} LastAdded: {1} LastDisplayed: {2}\n".format(
                hist.CommandCount, hist.LastAdded, hist.LastDisplayed))
            outfd.write("FirstCommand: {0} CommandCountMax: {1}\n".format(
                hist.FirstCommand, hist.CommandCountMax))
            outfd.write("ProcessHandle: {0:#x}\n".format(hist.ProcessHandle))

            # If the _COMMAND_HISTORY is in use, we would only take 
            # hist.CommandCount but since we're brute forcing, try the 
            # maximum and hope that some slots were not overwritten 
            # or zero-ed out. 
            pointers = obj.Object("Array", targetType = "address",
                        count = hist.CommandCountMax,
                        offset = hist.obj_offset +
                        hist.obj_vm.profile.get_obj_offset("_COMMAND_HISTORY", "CommandBucket"),
                        vm = hist.obj_vm)

            for i, p in enumerate(pointers):
                cmd = p.dereference_as("_COMMAND")
                if cmd and str(cmd.Cmd):
                    outfd.write("Cmd #{0} @ {1:#x}: {2}\n".format(
                        i, cmd.obj_offset, str(cmd.Cmd)))

#--------------------------------------------------------------------------------
# Consoles Plugin 
#--------------------------------------------------------------------------------

class Consoles(CmdScan):
    """Extract command history by scanning for _CONSOLE_INFORMATION"""

    def __init__(self, config, *args, **kwargs):
        CmdScan.__init__(self, config, *args, **kwargs)
        # The default comes from HKCU\Console\NumberOfHistoryBuffers
        config.add_option('HISTORY_BUFFERS', short_option = 'B', default = 4,
                            action = 'store', type = 'int',
                            help = 'HistoryBufferMax (default = 4)')

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

        srch_pattern = chr(self._config.MAX_HISTORY) + "\x00"

        for task in self.cmdhistory_process_filter(addr_space):
            for found in task.search_process_memory([srch_pattern]):

                console = obj.Object("_CONSOLE_INFORMATION",
                    offset = found -
                    addr_space.profile.get_obj_offset("_CONSOLE_INFORMATION", "CommandHistorySize"),
                    vm = task.get_process_address_space(),
                    parent = task)

                if (console.HistoryBufferMax != self._config.HISTORY_BUFFERS or
                    console.HistoryBufferCount > self._config.HISTORY_BUFFERS):
                    continue

                # Check the first command history as the final constraint 
                history = obj.Object("_COMMAND_HISTORY",
                    offset = console.HistoryList.Flink.dereference().obj_offset -
                    addr_space.profile.get_obj_offset("_COMMAND_HISTORY", "ListEntry"),
                    vm = task.get_process_address_space())

                if history.CommandCountMax != self._config.MAX_HISTORY:
                    continue

                yield task, console

    def render_text(self, outfd, data):

        for task, console in data:

            outfd.write("*" * 50 + "\n")
            outfd.write("ConsoleProcess: {0} Pid: {1}\n".format(
                task.ImageFileName, task.UniqueProcessId))
            outfd.write("Console: {0:#x} CommandHistorySize: {1}\n".format(
                console.obj_offset, console.CommandHistorySize))
            outfd.write("HistoryBufferCount: {0} HistoryBufferMax: {1}\n".format(
                console.HistoryBufferCount, console.HistoryBufferMax))
            outfd.write("OriginalTitle: {0}\n".format(console.OriginalTitle.dereference()))
            outfd.write("Title: {0}\n".format(console.Title.dereference()))

            for console_proc in console.get_processes():
                process = console_proc.reference_object_by_handle()
                if process:
                    outfd.write("AttachedProcess: {0} Pid: {1} Handle: {2:#x}\n".format(
                        process.ImageFileName, process.UniqueProcessId,
                        console_proc.ProcessHandle))

            for hist in console.get_histories():
                outfd.write("----\n")
                outfd.write("CommandHistory: {0:#x} Application: {1} Flags: {2}\n".format(
                    hist.obj_offset, hist.Application.dereference(),
                    hist.Flags))
                outfd.write("CommandCount: {0} LastAdded: {1} LastDisplayed: {2}\n".format(
                    hist.CommandCount, hist.LastAdded, hist.LastDisplayed))
                outfd.write("FirstCommand: {0} CommandCountMax: {1}\n".format(
                    hist.FirstCommand, hist.CommandCountMax))
                outfd.write("ProcessHandle: {0:#x}\n".format(hist.ProcessHandle))
                for i, cmd in hist.get_commands():
                    if cmd.Cmd:
                        outfd.write("Cmd #{0} at {1:#x}: {2}\n".format(
                            i, cmd.obj_offset, str(cmd.Cmd)))

            for exe_alias in console.get_exe_aliases():
                for alias in exe_alias.get_aliases():
                    outfd.write("----\n")
                    outfd.write("Alias: {0} Source: {1} Target: {2}\n".format(
                        exe_alias.ExeName.dereference(), alias.Source.dereference(),
                        alias.Target.dereference()))

            for screen in console.get_screens():
                outfd.write("----\n")
                outfd.write("Screen {0:#x} X:{1} Y:{2}\n".format(
                    screen.dereference(), screen.ScreenX, screen.ScreenY))
                outfd.write("Dump:\n{0}\n".format('\n'.join(screen.get_buffer())))
