#!/usr/bin/env python
from memutil import virt2phys, PagedOutException
from general import get_flags
from struct import unpack, calcsize
# Various flag fields used by objects in this file
VAD_FLAGS = {
"CommitCharge": 0x0007FFFF, # Mask
"PhysicalMapping": 0x00080000,
"ImageMap": 0x00100000,
"UserPhysicalPages": 0x00200000,
"NoChange": 0x00400000,
"WriteWatch": 0x00800000,
"Protection": 0x1F000000, # Mask
"LargePages": 0x20000000,
"MemCommit": 0x40000000,
"PrivateMemory": 0x80000000
}
# Only applies to _MMVAD and _MMVAD_LONG
VAD_FLAGS2 = {
"FileOffset": 0x00FFFFFF, # Mask
"SecNoChange": 0x01000000,
"OneSecured": 0x02000000,
"MultipleSecured": 0x04000000,
"ReadOnly": 0x08000000,
"LongVad": 0x10000000,
"ExtendableFile": 0x20000000,
"Inherit": 0x40000000,
"CopyOnWrite": 0x80000000
}
SECTION_FLAGS = {
'BeingDeleted': 0x00000001,
'BeingCreated': 0x00000002,
'BeingPurged': 0x00000004,
'NoModifiedWriting': 0x00000008,
'FailAllIo': 0x00000010,
'Image': 0x00000020,
'Based': 0x00000040,
'File': 0x00000080,
'Networked': 0x00000100,
'NoCache': 0x00000200,
'PhysicalMemory': 0x00000400,
'CopyOnWrite': 0x00000800,
'Reserve': 0x00001000,
'Commit': 0x00002000,
'FloppyMedia': 0x00004000,
'WasPurged': 0x00008000,
'UserReference': 0x00010000,
'GlobalMemory': 0x00020000,
'DeleteOnClose': 0x00040000,
'FilePointerNull': 0x00080000,
'DebugSymbolsLoaded': 0x00100000,
'SetMappedFileIoComplete': 0x00200000,
'CollidedFlush': 0x00400000,
'NoChange': 0x00800000,
'HadUserReference': 0x01000000,
'ImageMappedInSystemSpace': 0x02000000,
'UserWritable': 0x04000000,
'Accessed': 0x08000000,
'GlobalOnlyPerSession': 0x10000000,
'Rom': 0x20000000,
'filler': 0xc0000000 # Mask
}
VAD_FMT_SHORT = "<LLLLLL"
VAD_FMT = "<LLLL"
VAD_FMT_LONG = "<LLL"
CA_FMT = "<LLLLLLHHLLLLHH"
FILE_OBJ_FMT = "<HHLLLLLLlL8cLHHLqLLL16s16sL"
KEVENT_FMT = "<4clLL"
# Exception classes used by VAD trees
class InvalidVAD(Exception):
"""Exception used to indicate that some portion of tree creation failed."""
class DeleteMe(Exception):
"""Exception raised by lower level to indicate that subtree cannot be created."""
# Several other classes referenced by VAD nodes -- these should probably
# be moved to their own file eventually
# lkd> dt -r _KEVENT
# +0x000 Header : _DISPATCHER_HEADER
# +0x000 Type : UChar
# +0x001 Absolute : UChar
# +0x002 Size : UChar
# +0x003 Inserted : UChar
# +0x004 SignalState : Int4B
# +0x008 WaitListHead : _LIST_ENTRY
# +0x000 Flink : Ptr32 _LIST_ENTRY
# +0x004 Blink : Ptr32 _LIST_ENTRY
class KEvent:
def __init__(self,str):
(self.Type, # char -- why?
self.Absolute, # char -- why?
self.Size, # char -- why?
self.Inserted, # char -- why?
self.SignalState,
self.WaitList_Flink,
self.WaitList_Blink) = unpack(KEVENT_FMT, str)
def __str__(self):
return ("_KEVENT Type %x Absolute %x Size %x Inserted %x SignalState %d Flink %08x Blink %08x" %
(ord(self.Type), ord(self.Absolute), ord(self.Size), ord(self.Inserted),
self.SignalState, self.WaitList_Flink, self.WaitList_Blink))
# lkd> dt _FILE_OBJECT
# +0x000 Type : Int2B
# +0x002 Size : Int2B
# +0x004 DeviceObject : Ptr32 _DEVICE_OBJECT
# +0x008 Vpb : Ptr32 _VPB
# +0x00c FsContext : Ptr32 Void
# +0x010 FsContext2 : Ptr32 Void
# +0x014 SectionObjectPointer : Ptr32 _SECTION_OBJECT_POINTERS
# +0x018 PrivateCacheMap : Ptr32 Void
# +0x01c FinalStatus : Int4B
# +0x020 RelatedFileObject : Ptr32 _FILE_OBJECT
# +0x024 LockOperation : UChar
# +0x025 DeletePending : UChar
# +0x026 ReadAccess : UChar
# +0x027 WriteAccess : UChar
# +0x028 DeleteAccess : UChar
# +0x029 SharedRead : UChar
# +0x02a SharedWrite : UChar
# +0x02b SharedDelete : UChar
# +0x02c Flags : Uint4B
# +0x030 FileName : _UNICODE_STRING
# +0x038 CurrentByteOffset : _LARGE_INTEGER
# +0x040 Waiters : Uint4B
# +0x044 Busy : Uint4B
# +0x048 LastLock : Ptr32 Void
# +0x04c Lock : _KEVENT
# +0x05c Event : _KEVENT
# +0x06c CompletionContext : Ptr32 _IO_COMPLETION_CONTEXT
class FileObject:
def __init__(self,memdump, pdba, address):
self.address = address
self.pdba = pdba
self.memdump = memdump
try:
address_real = virt2phys(memdump, pdba, address)
self.invalid = False
except PagedOutException:
self.invalid = True
return
memdump.seek(address_real)
fobj_size = calcsize(FILE_OBJ_FMT)
(self.Type,
self.Size,
self.DeviceObject,
self.Vpb,
self.FsContext,
self.FsContext2,
self.SectionObjectPointer,
self.PrivateCacheMap,
self.FinalStatus,
self.RelatedFileObject,
self.LockOperation,
self.DeletePending,
self.ReadAccess,
self.WriteAccess,
self.DeleteAccess,
self.SharedRead,
self.SharedWrite,
self.SharedDelete,
self.Flags,
FileNameLen,
FileNameMaxLen,
FileNamePointer,
self.CurrentByteOffset,
self.Waiters,
self.Busy,
self.LastLock,
self.Lock, # _KEVENT blob
self.Event, # _KEVENT blob
self.CompletionContext) = unpack(FILE_OBJ_FMT,memdump.read(fobj_size))
try:
FileNamePointer_real = virt2phys(self.memdump,self.pdba,FileNamePointer)
memdump.seek(FileNamePointer_real)
self.FileName = memdump.read(FileNameLen)
except PagedOutException:
self.FileName = "[paged out]"
try:
self.FileName = self.FileName.decode('utf_16_le')
except UnicodeDecodeError:
print "[WARN] Unable to decode Unicode filename; file object @%08x may not be valid" % self.address
self.Lock = KEvent(self.Lock)
self.Event = KEvent(self.Event)
def __str__(self):
if self.invalid: return "FileObject @%08x [paged out]" % self.address
phys_addr = virt2phys(self.memdump, self.pdba, self.address)
return "FileObject @%08x (%08x), Name: %s" % (self.address, phys_addr, self.FileName)
# +0x000 Segment : Ptr32 _SEGMENT
# +0x004 DereferenceList : _LIST_ENTRY
# +0x000 Flink : Ptr32 _LIST_ENTRY
# +0x004 Blink : Ptr32 _LIST_ENTRY
# +0x00c NumberOfSectionReferences : Uint4B
# +0x010 NumberOfPfnReferences : Uint4B
# +0x014 NumberOfMappedViews : Uint4B
# +0x018 NumberOfSubsections : Uint2B
# +0x01a FlushInProgressCount : Uint2B
# +0x01c NumberOfUserReferences : Uint4B
# +0x020 u : __unnamed
# +0x000 Flags : _MMSECTION_FLAGS (See SECTION_FLAGS dict above)
# +0x024 FilePointer : Ptr32 _FILE_OBJECT
# +0x028 WaitingForDeletion : Ptr32 _EVENT_COUNTER
# +0x02c ModifiedWriteCount : Uint2B
# +0x02e NumberOfSystemCacheViews : Uint2B
class ControlArea:
def __init__(self,memdump, pdba, address):
self.address = address
self.pdba = pdba
self.memdump = memdump
try:
address_real = virt2phys(memdump, pdba, address)
self.invalid = False
except PagedOutException:
self.invalid = True
return
memdump.seek(address_real)
ca_size = calcsize(CA_FMT)
(self.Segment_address,
self.DereferenceList_flink,
self.DereferenceList_blink,
self.NumberOfSectionReferences,
self.NumberOfPfnReferences,
self.NumberOfMappedViews,
self.NumberOfSubsections,
self.FlushInProgressCount,
self.NumberOfUserReferences,
self.flags,
file_obj_addr,
self.WaitingForDeletion_addr,
self.ModifiedWriteCount,
self.NumberOfSystemCacheViews) = unpack(CA_FMT,memdump.read(ca_size))
if file_obj_addr:
self.FileObject = FileObject(memdump, pdba, file_obj_addr)
else:
self.FileObject = None
def __str__(self):
if self.invalid: return "ControlArea [paged out]"
ostr = ""
ostr += "ControlArea @%08x Segment %08x" % (self.address, self.Segment_address)
ostr += "\n"
ostr += "Dereference list: Flink %08x, Blink %08x" % (self.DereferenceList_flink,
self.DereferenceList_blink)
ostr += "\n"
ostr += "NumberOfSectionReferences: %10d NumberOfPfnReferences: %10d\n" % (self.NumberOfSectionReferences,
self.NumberOfPfnReferences)
ostr += "NumberOfMappedViews: %10d NumberOfSubsections: %10d\n" % (self.NumberOfMappedViews,
self.NumberOfSubsections)
ostr += "FlushInProgressCount: %10d NumberOfUserReferences: %10d\n" % (self.FlushInProgressCount,
self.NumberOfUserReferences)
ostr += "Flags: " + ", ".join(get_flags(self.flags,SECTION_FLAGS,ignore=['filler']))
ostr += "\n"
if self.FileObject:
ostr += str(self.FileObject)
else:
ostr += "FileObject: none"
ostr += "\n"
ostr += "WaitingForDeletion Event: %08x\n" % self.WaitingForDeletion_addr
ostr += "ModifiedWriteCount: %8d NumberOfSystemCacheViews: %8d" % (self.ModifiedWriteCount,
self.NumberOfSystemCacheViews)
return ostr
class VAD:
def __init__(self, parent, pdba, memdump, address, level=0, allow_invalid=False):
"""Create a new VAD object. Will recursively load children.
Arguments:
parent - VAD object that is this VAD's parent
pdba - Page Directory Base for this process
memdump - file pointer to memory dump
address - (virtual) address of VAD entry
level - recursion level (used internally, usually no reason to set this)
allow_invalid - force the generation of the tree, even if some subtrees
are paged out. Otherwise an InvalidVAD exception will be raised.
"""
#print "DEBUG: recursion level %d" % level
self.level = level
self.parent = parent
self.pdba = pdba
self.memdump = memdump
self.address = address
try:
address_real = virt2phys(memdump, pdba, address)
#print "DEBUG: reading VAD at %x (%x real)" % (address, address_real)
except PagedOutException:
if allow_invalid:
print "WARN: subtree paged out, returning."
raise DeleteMe()
else:
raise InvalidVAD("VAD tree is invalid; some subtree"
" is paged out (depth: %d)" % self.level)
# Cheat for a sec to get our tag
memdump.seek(address_real - 4)
self.tag = memdump.read(4)
# All VADs support at least this
vad_size = calcsize(VAD_FMT_SHORT)
(start_address, end_address, parent_addr,
left_addr, right_addr, flags) = unpack(VAD_FMT_SHORT, memdump.read(vad_size))
self.start_address = (start_address << 12)
# Based on the source code in Undocumented Windows Internals, it
# appears that end_address specifies the base of the last page
# used by the process (the calculation they have to list the full
# range in bytes is ((end_address+1) << 12) - 1. I'm choosing to set
# this to the address of the first unused byte, as this fits better
# with python's range operations.
self.end_address = ((end_address+1) << 12)
self.flags = flags
# Load extra information if we can
if self.tag == "Vad " or self.tag == "Vadl":
vad_size = calcsize(VAD_FMT)
(control_area_addr, first_prototype_pte,
last_contiguous_pte, flags2) = unpack(VAD_FMT,memdump.read(vad_size))
if control_area_addr:
self.ControlArea = ControlArea(memdump, pdba, control_area_addr)
else: self.ControlArea = None
self.first_prototype_pte = first_prototype_pte
self.last_contiguous_pte = last_contiguous_pte
self.flags2 = flags2
else:
self.ControlArea = None
self.first_prototype_pte = None
self.last_contiguous_pte = None
self.flags2 = None
# Even bigger VAD -- not often seen
if self.tag == "Vadl" or (self.flags2 and
self.flags2 & VAD_FLAGS2['LongVad']):
vad_size = calcsize(VAD_FMT_LONG)
(secured_start, secured_end,
extended_info_addr) = unpack(VAD_FMT_LONG,memdump.read(vad_size))
self.secured_start = secured_start
self.secured_end = secured_end
self.extended_info_addr = extended_info_addr
else:
self.secured_start = None
self.secured_end = None
self.extended_info_addr = None
# Sanity check: parent's address is correct
if self.parent is not None:
try: assert self.parent.address == parent_addr
except AssertionError:
print "WARN: Parent addresses do not match: expected %x, got %x" % (self.parent.address,
parent_addr)
# Recursively load left and right subtrees, if they exist
if left_addr != 0:
try: self.left = VAD(self, pdba, memdump, left_addr, level+1)
except DeleteMe: self.left = None
else: self.left = None
if right_addr != 0:
try: self.right = VAD(self, pdba, memdump, right_addr, level+1)
except DeleteMe: self.right = None
else: self.right = None
def print_as_table(self):
if self.parent is None:
print "Address Parent Left Right Start End Tag Flags"
else: parent_address = self.parent.address
if self.parent: parent_address = self.parent.address
else: parent_address = 0
if self.left: left_address = self.left.address
else: left_address = 0
if self.right: right_address = self.right.address
else: right_address = 0
print "%08x %08x %08x %08x %08x %08x %-4s %s %08x" % (self.address,
parent_address, left_address,
right_address, self.start_address,
self.end_address,self.tag,
",".join(get_flags(self.flags, VAD_FLAGS,
ignore=["Protection", "CommitCharge"])),
virt2phys(self.memdump,self.pdba,self.address))
if self.left: self.left.print_as_table()
if self.right: self.right.print_as_table()
def print_as_tree(self):
print " "*self.level + "%x - %x" % (self.start_address, self.end_address)
if self.left: self.left.print_as_tree()
if self.right: self.right.print_as_tree()
def make_dot(self):
# Add header
if not self.parent:
current_dotstring = 'digraph processtree {\n'
current_dotstring += 'graph [rankdir = "TB"];\n'
else:
current_dotstring = ''
# Add self
current_dotstring += ('vad_%x [label = "{ %08x - %08x }" shape = "record" color = "blue"];\n' %
(self.address, self.start_address, self.end_address))
# Add subtrees
if self.left:
current_dotstring += 'vad_%x -> vad_%x\n' % (self.address, self.left.address)
current_dotstring += self.left.make_dot()
if self.right:
current_dotstring += 'vad_%x -> vad_%x\n' % (self.address, self.right.address)
current_dotstring += self.right.make_dot()
# Add footer
if not self.parent:
current_dotstring += '}\n'
return current_dotstring
def __iter__(self):
yield self
if self.left:
for node in self.left:
yield node
if self.right:
for node in self.right:
yield node
def __repr__(self):
return ("<VAD node %08x-%08x, flags %08x, level %d>" %
(self.start_address,self.end_address,self.flags,
self.level))
def __str__(self):
ostr = "VAD node @%08x Start %08x End %08x Level %d Tag %4s" % (self.address,
self.start_address,self.end_address,self.level,self.tag)
ostr += '\n'
ostr += "Flags: " + ", ".join(get_flags(self.flags,VAD_FLAGS,ignore=['CommitCharge','Protection']))
ostr += '\n'
ostr += "Commit Charge: %d Protection: %x" % (self.flags & VAD_FLAGS['CommitCharge'],
(self.flags & VAD_FLAGS['Protection']) >> 24)
ostr += '\n'
# End of base VAD attributes
if self.ControlArea:
ostr += str(self.ControlArea)
ostr += '\n'
if self.first_prototype_pte is not None:
ostr += "First prototype PTE: %08x " % self.first_prototype_pte
if self.last_contiguous_pte is not None:
ostr += "Last contiguous PTE: %08x" % self.last_contiguous_pte
ostr += '\n'
if self.flags2 is not None:
ostr += 'Flags2: ' + ", ".join(get_flags(self.flags2,VAD_FLAGS2,ignore=['FileOffset']))
ostr += '\n'
if self.flags2 is not None:
ostr += "File offset: %08x" % (self.flags2 & VAD_FLAGS2['FileOffset'])
ostr += '\n'
# Even more attributes!
if (self.secured_start is not None and
self.secured_end is not None):
ostr += "Secured: %08x - %08x" % (self.secured_start,self.secured_end)
ostr += '\n'
if self.extended_info_addr is not None:
ostr += "Pointer to _MMEXTEND_INFO (or _MMBANKED_SECTION ?): %08x" % self.extended_info_addr
try:
extended_info_real = virt2phys(self.memdump, self.pdba, self.extended_info_addr)
ostr += " (%08x)" % extended_info_real
except:
pass
ostr += '\n'
return ostr