mirror of
https://github.com/volatilityfoundation/volatility
synced 2026-06-08 18:04:46 +00:00
779 lines
33 KiB
Python
779 lines
33 KiB
Python
# 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())))
|