def windows_insert_module_from_base(base, size, pid, pgd, name, fullname): ''' Insert a module given its base ''' from vmi import modules from utils import get_addr_space if pgd != 0: addr_space = get_addr_space(pgd) else: addr_space = get_addr_space() nt_header = obj.Object("_IMAGE_DOS_HEADER", offset=base, vm=addr_space) checksum = nt_header.OptionalHeader.CheckSum.v() if (pid, pgd) not in modules: modules[(pid, pgd)] = {} windows_insert_module_internal( pid, pgd, base, size, fullname, name, checksum, nt_header, update_symbols=True)
def windows_update_modules(pgd, update_symbols=False): ''' Use volatility to get the modules and symbols for a given process, and update the cache accordingly ''' import api from utils import get_addr_space from vmi import modules if pgd != 0: addr_space = get_addr_space(pgd) else: addr_space = get_addr_space() if addr_space is None: pp_error("Volatility address space not loaded\n") return # Get EPROC directly from its offset procs = api.get_process_list() inserted_bases = [] # Parse/update kernel modules: if last_kdbg is not None: kdbg = obj.Object( "_KDDEBUGGER_DATA64", offset=last_kdbg, vm=addr_space) for module in kdbg.modules(): if module.DllBase not in inserted_bases: inserted_bases.append(module.DllBase) windows_insert_module(0, 0, module, update_symbols) for proc in procs: p_pid = proc["pid"] p_pgd = proc["pgd"] # p_name = proc["name"] p_kernel_addr = proc["kaddr"] if p_pgd == pgd: task = obj.Object("_EPROCESS", offset=p_kernel_addr, vm=addr_space) # Note: we do not erase the modules we have information for from the list, # unless we have a different module loaded at the same base address. # In this way, if at some point the module gets unmapped from the PEB list # but it is still in memory, we do not loose the information. if (p_pid, p_pgd) not in modules: modules[(p_pid, p_pgd)] = {} for module in task.get_init_modules(): if module.DllBase not in inserted_bases: inserted_bases.append(module.DllBase) windows_insert_module(p_pid, p_pgd, module, update_symbols) for module in task.get_mem_modules(): if module.DllBase not in inserted_bases: inserted_bases.append(module.DllBase) windows_insert_module(p_pid, p_pgd, module, update_symbols) for module in task.get_load_modules(): if module.DllBase not in inserted_bases: inserted_bases.append(module.DllBase) windows_insert_module(p_pid, p_pgd, module, update_symbols) return
def windows_insert_module_internal( p_pid, p_pgd, base, size, fullname, basename, checksum, nt_header, update_symbols): from utils import get_addr_space from vmi import modules from vmi import symbols from vmi import Module from vmi import PseudoLDRDATA mod = Module(base, size, p_pid, p_pgd, checksum, basename, fullname) if p_pgd != 0: addr_space = get_addr_space(p_pgd) else: addr_space = get_addr_space() # Getting symbols, from cache! if (checksum, fullname) in symbols: mod.set_symbols(symbols[(checksum, fullname)]) elif update_symbols: syms = [] export_dir = nt_header.OptionalHeader.DataDirectory[0] if export_dir: expdir = obj.Object( '_IMAGE_EXPORT_DIRECTORY', offset=base + export_dir.VirtualAddress, vm=addr_space, parent=PseudoLDRDATA( base, basename, export_dir)) if expdir.valid(nt_header): # Ordinal, Function RVA, and Name Object for o, f, n in expdir._exported_functions(): if not isinstance(o, obj.NoneObject) and \ not isinstance(f, obj.NoneObject) and \ not isinstance(n, obj.NoneObject): syms.append((o, f.v(), str(n))) symbols[(checksum, fullname)] = syms mod.set_symbols(syms) if base in modules[(p_pid, p_pgd)]: del modules[(p_pid, p_pgd)][base] modules[(p_pid, p_pgd)][base] = mod
def windows_insert_module_internal(p_pid, p_pgd, base, size, fullname, basename, checksum, nt_header, update_symbols): import volatility.conf as volconf import volatility.registry as registry import volatility.commands as commands import volatility.addrspace as addrspace import volatility.constants as constants import volatility.exceptions as exceptions import volatility.obj as obj import volatility.scan as scan import volatility.plugins.kdbgscan as kdbg import volatility.win32.tasks as tasks import volatility.utils as utils from utils import get_addr_space from vmi import modules from vmi import symbols from vmi import Module from vmi import PseudoLDRDATA mod = Module(base, size, p_pid, p_pgd, checksum, basename, fullname) if p_pgd != 0: addr_space = get_addr_space(p_pgd) else: addr_space = get_addr_space() #Getting symbols, from cache! if (checksum, fullname) in symbols: mod.set_symbols(symbols[(checksum, fullname)]) elif update_symbols: syms = [] export_dir = nt_header.OptionalHeader.DataDirectory[0] if export_dir: expdir = obj.Object('_IMAGE_EXPORT_DIRECTORY', offset=base + export_dir.VirtualAddress, vm=addr_space, parent=PseudoLDRDATA(base, basename, export_dir)) if expdir.valid(nt_header): # Ordinal, Function RVA, and Name Object for o, f, n in expdir._exported_functions(): if not type(o) is obj.NoneObject and \ not type(f) is obj.NoneObject and \ not type(n) is obj.NoneObject: syms.append((o, f.v(), str(n))) symbols[(checksum, fullname)] = syms mod.set_symbols(syms) if base in modules[(p_pid, p_pgd)]: del modules[(p_pid, p_pgd)][base] modules[(p_pid, p_pgd)][base] = mod
def get_stacks(pgd): ''' Get list of VAD regions using volatility ''' import volatility.obj as obj import volatility.win32.tasks as tasks import volatility.plugins.vadinfo as vadinfo from utils import get_addr_space stack_list = [] # Get volatility address space using the function in utils addr_space = get_addr_space(pgd) # Get list of Task objects using volatility (EPROCESS executive objects) eprocs = [ t for t in tasks.pslist(addr_space) if t.Pcb.DirectoryTableBase.v() == pgd ] # Traverse the list of selected EPROCESSes for task in eprocs: # Get Stack base for every THREAD for thread in task.ThreadListHead.list_of_type("_ETHREAD", "ThreadListEntry"): teb = obj.Object("_TEB", offset=thread.Tcb.Teb, vm=task.get_process_address_space()) if teb: stack_list.append( (teb.NtTib.StackBase.v(), teb.NtTib.StackLimit.v())) return stack_list
def dump(pgd_list, path = "/tmp/"): ''' Dump the process, modules, vads..., given a list of process address spaces and a path. ''' import volatility.constants as constants import volatility.exceptions as exceptions import volatility.obj as obj import volatility.win32.tasks as tasks from utils import get_addr_space import api # Delete contents, and create directory under path if os.path.isdir(path): shutil.rmtree(path) os.mkdir(path) try: # Get volatility address space addr_space = get_addr_space() # Get list of processes (tasks) from volatility eprocs = [t for t in tasks.pslist( addr_space) if t.Pcb.DirectoryTableBase.v() in pgd_list] # For every selected task for task in eprocs: # Code adapted from procdump (volatility) task_space = task.get_process_address_space() if task_space is None: print("Error: Cannot acquire process AS") return elif task.Peb is None: # we must use m() here, because any other attempt to # reference task.Peb will try to instantiate the _PEB print( "Error: PEB at {0:#x} is unavailable (possibly due to paging)".format(task.m('Peb'))) return elif task_space.vtop(task.Peb.ImageBaseAddress) is None: print( "Error: ImageBaseAddress at {0:#x} is unavailable" + "(possibly due to paging)".format(task.Peb.ImageBaseAddress)) return else: dump_file = os.path.join( path, "PID_%x.executable.ex_" % (task.UniqueProcessId)) of = open(dump_file, 'wb') pe_file = obj.Object( "_IMAGE_DOS_HEADER", offset=task.Peb.ImageBaseAddress, vm=task_space) try: for offset, code in pe_file.get_image(unsafe=True, memory=False, fix=True): of.seek(offset) of.write(code) except ValueError, ve: print("Error: {0}".format(ve)) return except exceptions.SanityCheckException, ve: print("Error: {0} Try -u/--unsafe".format(ve)) return
def windows_get_prototype_pte_address_range(pgd, address): import volatility.obj as obj import volatility.win32.tasks as tasks import volatility.plugins.vadinfo as vadinfo from utils import get_addr_space addr_space = get_addr_space(pgd) eprocs = [ t for t in tasks.pslist(addr_space) if t.Pcb.DirectoryTableBase.v() == pgd ] if len(eprocs) != 1: return None task = eprocs[0] vad = None # File name and offset for vad in task.VadRoot.traverse(): if address >= vad.Start and address < vad.End: break if vad is None: return None if vad.ControlArea is not None and vad.ControlArea.Segment is not None: start = vad.ControlArea.Segment.PrototypePte.v() end = start + (vad.ControlArea.Segment.TotalNumberOfPtes.v() * addr_space.profile.vtypes["_MMPTE_SOFTWARE"][0]) return (start, end) else: return None
def update_from_peb(self): ''' Update several variables based on info extracted from peb ''' import volatility.win32.tasks as tasks from utils import get_addr_space addr_space = get_addr_space(self.get_pgd()) eprocs = [ t for t in tasks.pslist(addr_space) if t.UniqueProcessId == self.__pid ] if len(eprocs) != 1: self.__commandline = None self.__current_directory = None self.__image_path = None else: task = eprocs[0] self.__commandline = str(task.Peb.ProcessParameters.CommandLine or '') self.__current_directory = str( task.Peb.ProcessParameters.CurrentDirectory.DosPath or '') self.__image_path = str(task.Peb.ProcessParameters.ImagePathName or '')
def get_system_time(): from volatility import obj from utils import get_addr_space addr_space = get_addr_space() k = obj.Object("_KUSER_SHARED_DATA", offset=obj.VolMagic(addr_space).KUSER_SHARED_DATA.v(), vm=addr_space) return k.SystemTime.as_datetime()
def new_proc(pid,pgd,name): global cm pyrebox_print("Process %x started with pgd: %x. Name: %s" % (pid,pgd,name)) #Get the volatility address space, adjusted for our current pgd addr_space = get_addr_space(pgd) #Use the pslist function to retrieve the process list with volatility procs = [t for t in tasks.pslist(addr_space)] #Just print the process list for p in procs: pyrebox_print("Process %s PID:%x" % (p.ImageFileName,p.UniqueProcessId))
def windows_insert_module_from_base(base, size, pid, pgd, name, fullname): ''' Insert a module given its base ''' import volatility.conf as volconf import volatility.registry as registry import volatility.commands as commands import volatility.addrspace as addrspace import volatility.constants as constants import volatility.exceptions as exceptions import volatility.obj as obj import volatility.scan as scan import volatility.plugins.kdbgscan as kdbg import volatility.win32.tasks as tasks import volatility.utils as utils from utils import get_addr_space from vmi import modules from vmi import symbols from vmi import Module from vmi import PseudoLDRDATA if pgd != 0: addr_space = get_addr_space(pgd) else: addr_space = get_addr_space() nt_header = obj.Object("_IMAGE_DOS_HEADER", offset=base, vm=addr_space) checksum = nt_header.OptionalHeader.CheckSum.v() if (pid, pgd) not in modules: modules[(pid, pgd)] = {} windows_insert_module_internal(pid, pgd, base, size, fullname, name, checksum, nt_header, update_symbols=True)
def new_proc(params): global cm pid = params["pid"] pgd = params["pgd"] name = params["name"] pyrebox_print("Process %x started with pgd: %x. Name: %s" % (pid, pgd, name)) # Get the volatility address space, adjusted for our current pgd addr_space = get_addr_space(pgd) # Use the pslist function to retrieve the process list with volatility procs = [t for t in tasks.pslist(addr_space)] # Just print the process list for p in procs: pyrebox_print("Process %s PID:%x" % (p.ImageFileName, p.UniqueProcessId))
def update_from_peb(self): ''' Update several variables based on info extracted from peb ''' import volatility.win32.tasks as tasks from utils import get_addr_space addr_space = get_addr_space(self.get_pgd()) eprocs = [t for t in tasks.pslist( addr_space) if t.UniqueProcessId == self.pid] if len(eprocs) != 1: self.commandline = None self.current_directory = None self.image_path = None else: task = eprocs[0] self.commandline = str( task.Peb.ProcessParameters.CommandLine or '') self.current_directory = str( task.Peb.ProcessParameters.CurrentDirectory.DosPath or '') self.image_path = str( task.Peb.ProcessParameters.ImagePathName or '')
def module_entry_point(proc, params): ''' Callback on the entry point of the main module being monitored ''' global cm global entry_point_bps global interproc_data from api import CallbackManager import api from utils import get_addr_space import volatility.win32.tasks as tasks # Get pameters cpu_index = params["cpu_index"] cpu = params["cpu"] # Get running process pgd = api.get_running_process(cpu_index) # Disable the entrypoint entry_point_bps[pgd].disable() # Call all our internal callbacks interproc_data.deliver_entry_point_callback(params) # Use volatility to check if it is a Wow64 process # Get volatility address space using the function in utils addr_space = get_addr_space(pgd) # Get list of Task objects using volatility (EPROCESS executive objects) eprocs = [t for t in tasks.pslist( addr_space) if t.Pcb.DirectoryTableBase.v() == pgd] if len(eprocs) > 0: proc.set_wow64(eprocs[0].IsWow64)
def ntopenprocessret(params, pid, callback_name, proc_hdl_p, proc, update_vads): import volatility.win32.tasks as tasks from mw_monitor_classes import mwmon from mw_monitor_classes import mw_monitor_start_monitoring_process from mw_monitor_classes import Process from api import get_running_process from utils import get_addr_space import api TARGET_LONG_SIZE = api.get_os_bits() / 8 cpu_index = params["cpu_index"] cpu = params["cpu"] pgd = get_running_process(cpu_index) # First, remove callback mwmon.cm.rm_callback(callback_name) # Do not continue if EAX/RAX returns and invalid return code. if read_return_parameter(cpu) != 0: return # Load volatility address space addr_space = get_addr_space(pgd) # Get list of processes, and filter out by the process that triggered the # call (current process id) eprocs = [t for t in tasks.pslist(addr_space) if t.UniqueProcessId == pid] # Initialize proc_obj, that will point to the eprocess of the new created # process proc_obj = None # Dereference the output argument containing the hdl of the newly created # process proc_hdl = dereference_target_long(proc_hdl_p, pgd) # Search handle table for the new created process for task in eprocs: if task.UniqueProcessId == pid and task.ObjectTable.HandleTableList: for handle in task.ObjectTable.handles(): if handle.is_valid( ) and handle.HandleValue == proc_hdl and handle.get_object_type( ) == "Process": proc_obj = handle.dereference_as("_EPROCESS") break break if proc_obj is not None: if mwmon.interproc_text_log and mwmon.interproc_text_log_handle is not None: f = mwmon.interproc_text_log_handle f.write("[PID: %x] NtOpenProcess: %s - PID: %x - CR3: %x\n" % (pid, str( proc_obj.ImageFileName), int(proc_obj.UniqueProcessId), int(proc_obj.Pcb.DirectoryTableBase.v()))) # Check if we are already monitoring the process for proc in mwmon.data.procs: if proc.pid == int(proc_obj.UniqueProcessId): return new_proc = Process(str(proc_obj.ImageFileName)) new_proc.set_pid(int(proc_obj.UniqueProcessId)) new_proc.set_pgd(int(proc_obj.Pcb.DirectoryTableBase.v())) mw_monitor_start_monitoring_process(new_proc) else: if TARGET_LONG_SIZE == 4: mwmon.printer( "Error while trying to retrieve EPROCESS for handle %x, PID %x, EAX: %x" % (proc_hdl, pid, cpu.EAX)) elif TARGET_LONG_SIZE == 8: mwmon.printer( "Error while trying to retrieve EPROCESS for handle %x, PID %x, EAX: %x" % (proc_hdl, pid, cpu.RAX)) if update_vads: proc.update_vads() return
def ntallocatevirtualmemory(params, pid, proc, update_vads): import volatility.win32.tasks as tasks from mw_monitor_classes import mwmon import api from api import CallbackManager from utils import get_addr_space TARGET_LONG_SIZE = api.get_os_bits() / 8 cpu_index = params["cpu_index"] cpu = params["cpu"] pgd = api.get_running_process(cpu_index) # IN HANDLE ProcessHandle, # IN OUT PVOID *BaseAddress, # IN ULONG ZeroBits, # IN OUT PULONG RegionSize, # IN ULONG AllocationType, # IN ULONG Protect ); # Only used for logging the event if not mwmon.interproc_text_log: return # Get call parameters (ret_addr, proc_handle, base_addr_p, zerobits, size_p, aloc_type, access) = read_parameters(cpu, 6) # Load volatility address space addr_space = get_addr_space(pgd) # Get list of processes, and filter out by the process that triggered the # call (current process id) eprocs = [t for t in tasks.pslist(addr_space) if t.UniqueProcessId == pid] # Initialize proc_obj, that will point to the object of the referenced # process, and section_obj, idem proc_obj = None # Search handle table for the caller process for task in eprocs: if task.UniqueProcessId == pid: if (TARGET_LONG_SIZE == 4 and proc_handle == 0xffffffff) or \ (TARGET_LONG_SIZE == 8 and proc_handle == 0xffffffffffffffff): proc_obj = task break elif task.UniqueProcessId == pid and task.ObjectTable.HandleTableList: for handle in task.ObjectTable.handles(): if handle.is_valid(): if handle.HandleValue == proc_handle and handle.get_object_type( ) == "Process": proc_obj = handle.dereference_as("_EPROCESS") break break mapping_proc = None if proc_obj is not None: for proc in mwmon.data.procs: if proc.pid == proc_obj.UniqueProcessId: mapping_proc = proc break if mapping_proc is not None: # Arguments to callback: the callback name, so that it can unset it, # the process handle variable, and the section handle callback_name = mwmon.cm.generate_callback_name( "ntallocatevirtualmemory_ret") callback_function = functools.partial(ntallocatevirtualmemory_ret, pid=pid, callback_name=callback_name, mapping_proc=mapping_proc, base_addr_p=base_addr_p, zerobits=zerobits, size_p=size_p, aloc_type=aloc_type, access=access, proc=proc, update_vads=update_vads) mwmon.cm.add_callback(CallbackManager.INSN_BEGIN_CB, callback_function, name=callback_name, addr=ret_addr, pgd=pgd)
def ntopenprocessret(params, pid, callback_name, proc_hdl_p, proc, update_vads): import volatility.win32.tasks as tasks from mw_monitor_classes import mwmon from mw_monitor_classes import mw_monitor_start_monitoring_process from mw_monitor_classes import Process from api import get_running_process from utils import get_addr_space import api TARGET_LONG_SIZE = api.get_os_bits() / 8 cpu_index = params["cpu_index"] cpu = params["cpu"] pgd = get_running_process(cpu_index) # First, remove callback mwmon.cm.rm_callback(callback_name) # Do not continue if EAX/RAX returns and invalid return code. if read_return_parameter(cpu) != 0: return # Load volatility address space addr_space = get_addr_space(pgd) # Get list of processes, and filter out by the process that triggered the # call (current process id) eprocs = [t for t in tasks.pslist(addr_space) if t.UniqueProcessId == pid] # Initialize proc_obj, that will point to the eprocess of the new created # process proc_obj = None # Dereference the output argument containing the hdl of the newly created # process proc_hdl = dereference_target_long(proc_hdl_p, pgd) # Search handle table for the new created process for task in eprocs: if task.UniqueProcessId == pid and task.ObjectTable.HandleTableList: for handle in task.ObjectTable.handles(): if handle.is_valid() and handle.HandleValue == proc_hdl and handle.get_object_type() == "Process": proc_obj = handle.dereference_as("_EPROCESS") break break if proc_obj is not None: if mwmon.interproc_text_log and mwmon.interproc_text_log_handle is not None: f = mwmon.interproc_text_log_handle f.write("[PID: %x] NtOpenProcess: %s - PID: %x - CR3: %x\n" % (pid, str(proc_obj.ImageFileName), int(proc_obj.UniqueProcessId), int(proc_obj.Pcb.DirectoryTableBase.v()))) # Check if we are already monitoring the process for proc in mwmon.data.procs: if proc.pid == int(proc_obj.UniqueProcessId): return new_proc = Process(str(proc_obj.ImageFileName)) new_proc.set_pid(int(proc_obj.UniqueProcessId)) new_proc.set_pgd(int(proc_obj.Pcb.DirectoryTableBase.v())) mw_monitor_start_monitoring_process(new_proc) else: if TARGET_LONG_SIZE == 4: mwmon.printer("Error while trying to retrieve EPROCESS for handle %x, PID %x, EAX: %x" % (proc_hdl, pid, cpu.EAX)) elif TARGET_LONG_SIZE == 8: mwmon.printer("Error while trying to retrieve EPROCESS for handle %x, PID %x, EAX: %x" % (proc_hdl, pid, cpu.RAX)) if update_vads: proc.update_vads() return
def ntwritevirtualmemory(params, pid, proc, update_vads, reverse=False): import volatility.win32.tasks as tasks from mw_monitor_classes import mwmon from mw_monitor_classes import Injection import api from utils import get_addr_space TARGET_LONG_SIZE = api.get_os_bits() / 8 cpu_index = params["cpu_index"] cpu = params["cpu"] # _In_ HANDLE ProcessHandle, # _In_ PVOID BaseAddress, # _In_ PVOID Buffer, # _In_ SIZE_T NumberOfBytesToWrite, # _Out_opt_ PSIZE_T NumberOfBytesWritten pgd = api.get_running_process(cpu_index) # Read the parameters ret_addr, proc_hdl, remote_addr, local_addr, size, size_out = read_parameters( cpu, 5) local_proc = None # Get local proc for proc in mwmon.data.procs: if proc.pid == pid: local_proc = proc break # Try to get remote process from list of monitored processes remote_proc = None # Load volatility address space addr_space = get_addr_space(pgd) # Get list of processes, and filter out by the process that triggered the # call (current process id) eprocs = [t for t in tasks.pslist(addr_space) if t.UniqueProcessId == pid] # Initialize proc_obj, that will point to the eprocess of the new created # process proc_obj = None # Search handle table for the new created process for task in eprocs: if task.UniqueProcessId == pid and task.ObjectTable.HandleTableList: for handle in task.ObjectTable.handles(): if handle.is_valid() and handle.HandleValue == proc_hdl and handle.get_object_type() == "Process": proc_obj = handle.dereference_as("_EPROCESS") break break if proc_obj is not None: for proc in mwmon.data.procs: if proc.pid == proc_obj.UniqueProcessId: remote_proc = proc break else: # Sometimes we get calls to this function over non-proc handles (e.g. type "Desktop") return if remote_proc is None: mwmon.printer( "[!] Could not obtain remote proc, or it is not being monitored") return elif local_proc is None: mwmon.printer( "[!] Could not obtain local proc, or it is not being monitored") return else: if reverse: data = None if mwmon.interproc_text_log and mwmon.interproc_text_log_handle is not None: f = mwmon.interproc_text_log_handle if TARGET_LONG_SIZE == 4: f.write("[PID: %x] NtReadVirtualMemory: PID: %x - Addr: %08x <-- PID: %x Addr: %08x / Size: %08x\n" % (pid, local_proc.pid, local_addr, remote_proc.pid, remote_addr, size)) elif TARGET_LONG_SIZE == 8: f.write("[PID: %x] NtReadVirtualMemory: PID: %x - Addr: %16x <-- PID: %x Addr: %16x / Size: %16x\n" % (pid, local_proc.pid, local_addr, remote_proc.pid, remote_addr, size)) else: data = None if mwmon.interproc_text_log and mwmon.interproc_text_log_handle is not None: f = mwmon.interproc_text_log_handle if TARGET_LONG_SIZE == 4: f.write("[PID: %x] NtWriteVirtualMemory: PID: %x - Addr: %08x --> PID: %x Addr: %08x / Size: %08x\n" % (pid, local_proc.pid, local_addr, remote_proc.pid, remote_addr, size)) elif TARGET_LONG_SIZE == 8: f.write("[PID: %x] NtWriteVirtualMemory: PID: %x - Addr: %16x --> PID: %x Addr: %16x / Size: %16x\n" % (pid, local_proc.pid, local_addr, remote_proc.pid, remote_addr, size)) inj = Injection(remote_proc, remote_addr, local_proc, local_addr, size, data, reverse) local_proc.add_injection(inj) if update_vads: proc.update_vads()
def ntmapviewofsection(params, pid, proc, update_vads): import volatility.obj as obj import volatility.win32.tasks as tasks import volatility.plugins.overlays.windows.windows as windows from mw_monitor_classes import mwmon from mw_monitor_classes import Section from utils import get_addr_space import api from api import CallbackManager TARGET_LONG_SIZE = api.get_os_bits() / 8 cpu_index = params["cpu_index"] cpu = params["cpu"] # IN HANDLE SectionHandle, # IN HANDLE ProcessHandle, # IN OUT PVOID *BaseAddress OPTIONAL, # IN ULONG ZeroBits OPTIONAL, # IN ULONG CommitSize, # IN OUT PLARGE_INTEGER SectionOffset OPTIONAL, # IN OUT PULONG ViewSize, # IN InheritDisposition, # IN ULONG AllocationType OPTIONAL, # IN ULONG Protect pgd = api.get_running_process(cpu_index) # Read the parameters ret_addr, section_handle, proc_handle, base_p, arg_3, arg_4, offset_p, size_p = read_parameters( cpu, 7) # Load volatility address space addr_space = get_addr_space(pgd) class _SECTION_OBJECT(obj.CType, windows.ExecutiveObjectMixin): def is_valid(self): return obj.CType.is_valid(self) addr_space.profile.object_classes.update( {'_SECTION_OBJECT': _SECTION_OBJECT}) # Get list of processes, and filter out by the process that triggered the # call (current process id) eprocs = [t for t in tasks.pslist(addr_space) if t.UniqueProcessId == pid] # Initialize proc_obj, that will point to the object of the referenced # process, and section_obj, idem proc_obj = None section_obj = None mapping_proc = None if (TARGET_LONG_SIZE == 4 and proc_handle == 0xffffffff) or ( TARGET_LONG_SIZE == 8 and proc_handle == 0xffffffffffffffff): for proc in mwmon.data.procs: if proc.pid == pid: mapping_proc = proc break # Search handle table for the caller process for task in eprocs: if task.UniqueProcessId == pid and task.ObjectTable.HandleTableList: for handle in task.ObjectTable.handles(): if handle.is_valid(): if not mapping_proc and not proc_obj and \ handle.HandleValue == proc_handle and \ handle.get_object_type() == "Process": proc_obj = handle.dereference_as("_EPROCESS") elif handle.HandleValue == section_handle and handle.get_object_type( ) == "Section": # We dereference the object as _SECTION_OBJECT, although it is not a _SECTION_OBJECT but a # _SECTION, that is not present in the volatility overlay: # http://forum.sysinternals.com/section-object_topic24975.html # For a better reference see the comments on the Section class # in mw_monitor_classes.py section_obj = handle.dereference_as("_SECTION_OBJECT") if (proc_obj or mapping_proc) and section_obj: break break # proc_obj represents the process over which the section is mapped # section_object represents the section being mapped. if (proc_obj is not None or mapping_proc is not None) and section_obj is not None: mapped_sec = None if not mapping_proc: for proc in mwmon.data.procs: if proc.pid == proc_obj.UniqueProcessId: mapping_proc = proc break if mapping_proc is None: mwmon.printer( "[!] The mapping process is not being monitored," + " a handle was obtained with an API different from " + "OpenProcess or CreateProcess") return for sec in mwmon.data.sections: if sec.get_offset() == section_obj.obj_offset: mapped_sec = sec break # If the section was not in our list, we create an entry if mapped_sec is None: mapped_sec = Section(pgd, section_obj) mwmon.data.sections.append(mapped_sec) # Record the actual map once we return back from the call and we can # dereference output parameters callback_name = mwmon.cm.generate_callback_name("mapviewofsection_ret") # Arguments to callback: the callback name, so that it can unset it, # the process handle variable, and the section handle callback_function = functools.partial(ntmapviewofsection_ret, pid=pid, callback_name=callback_name, mapping_proc=mapping_proc, mapped_sec=mapped_sec, base_p=base_p, size_p=size_p, offset_p=offset_p, proc=proc, update_vads=update_vads) mwmon.cm.add_callback(CallbackManager.INSN_BEGIN_CB, callback_function, name=callback_name, addr=ret_addr, pgd=pgd)
def ntvirtualprotect(params, pid, proc, update_vads): import volatility.win32.tasks as tasks from mw_monitor_classes import mwmon from utils import get_addr_space import api TARGET_LONG_SIZE = api.get_os_bits() / 8 cpu_index = params["cpu_index"] cpu = params["cpu"] pgd = api.get_running_process(cpu_index) # IN HANDLE ProcessHandle, # IN OUT PVOID *BaseAddress, # IN OUT PULONG NumberOfBytesToProtect, # IN ULONG NewAccessProtection, # OUT PULONG OldAccessProtection ); # Keep a log of page permissions for each VAD. Log changes for every virtualprotect call. # Output this informaiton on the log file, signal the sections with # changed permissions. ret_addr, proc_handle, base_addr_p, size_p, new_access, old_access = read_parameters( cpu, 5) # Load volatility address space addr_space = get_addr_space(pgd) # Get call parameters base_addr = dereference_target_long(base_addr_p, pgd) size = dereference_target_long(size_p, pgd) # Get list of processes, and filter out by the process that triggered the # call (current process id) eprocs = [t for t in tasks.pslist(addr_space) if t.UniqueProcessId == pid] # Initialize proc_obj, that will point to the object of the referenced # process, and section_obj, idem proc_obj = None # Search handle table for the caller process for task in eprocs: if task.UniqueProcessId == pid: if (TARGET_LONG_SIZE == 4 and proc_handle == 0xffffffff) or \ (TARGET_LONG_SIZE == 8 and proc_handle == 0xffffffffffffffff): proc_obj = task break elif task.UniqueProcessId == pid and task.ObjectTable.HandleTableList: for handle in task.ObjectTable.handles(): if handle.is_valid(): if handle.HandleValue == proc_handle and handle.get_object_type( ) == "Process": proc_obj = handle.dereference_as("_EPROCESS") break break mapping_proc = None if proc_obj is not None: for proc in mwmon.data.procs: if proc.pid == proc_obj.UniqueProcessId: mapping_proc = proc break if mapping_proc is not None: for v in mapping_proc.vads: # If the block overlaps the vad: if base_addr >= v.start and base_addr < (v.start + v.size): v.update_page_access(base_addr, size, new_access) if mwmon.interproc_text_log and mwmon.interproc_text_log_handle is not None: f = mwmon.interproc_text_log_handle if TARGET_LONG_SIZE == 4: f.write( "[PID: %x] NtVirtualProtect: Base: %08x Size: %08x NewProtect: %08x\n" % (pid, base_addr, size, new_access)) elif TARGET_LONG_SIZE == 8: f.write( "[PID: %x] NtVirtualProtect: Base: %016x Size: %016x NewProtect: %016x\n" % (pid, base_addr, size, new_access)) if update_vads: proc.update_vads()
def ntmapviewofsection(params, pid, proc, update_vads): import volatility.obj as obj import volatility.win32.tasks as tasks import volatility.plugins.overlays.windows.windows as windows from mw_monitor_classes import mwmon from mw_monitor_classes import Section from utils import get_addr_space import api from api import CallbackManager TARGET_LONG_SIZE = api.get_os_bits() / 8 cpu_index = params["cpu_index"] cpu = params["cpu"] # IN HANDLE SectionHandle, # IN HANDLE ProcessHandle, # IN OUT PVOID *BaseAddress OPTIONAL, # IN ULONG ZeroBits OPTIONAL, # IN ULONG CommitSize, # IN OUT PLARGE_INTEGER SectionOffset OPTIONAL, # IN OUT PULONG ViewSize, # IN InheritDisposition, # IN ULONG AllocationType OPTIONAL, # IN ULONG Protect pgd = api.get_running_process(cpu_index) # Read the parameters ret_addr, section_handle, proc_handle, base_p, arg_3, arg_4, offset_p, size_p = read_parameters( cpu, 7) # Load volatility address space addr_space = get_addr_space(pgd) class _SECTION_OBJECT(obj.CType, windows.ExecutiveObjectMixin): def is_valid(self): return obj.CType.is_valid(self) addr_space.profile.object_classes.update( {'_SECTION_OBJECT': _SECTION_OBJECT}) # Get list of processes, and filter out by the process that triggered the # call (current process id) eprocs = [t for t in tasks.pslist(addr_space) if t.UniqueProcessId == pid] # Initialize proc_obj, that will point to the object of the referenced # process, and section_obj, idem proc_obj = None section_obj = None mapping_proc = None if (TARGET_LONG_SIZE == 4 and proc_handle == 0xffffffff) or (TARGET_LONG_SIZE == 8 and proc_handle == 0xffffffffffffffff): for proc in mwmon.data.procs: if proc.pid == pid: mapping_proc = proc break # Search handle table for the caller process for task in eprocs: if task.UniqueProcessId == pid and task.ObjectTable.HandleTableList: for handle in task.ObjectTable.handles(): if handle.is_valid(): if not mapping_proc and not proc_obj and \ handle.HandleValue == proc_handle and \ handle.get_object_type() == "Process": proc_obj = handle.dereference_as("_EPROCESS") elif handle.HandleValue == section_handle and handle.get_object_type() == "Section": # We dereference the object as _SECTION_OBJECT, although it is not a _SECTION_OBJECT but a # _SECTION, that is not present in the volatility overlay: # http://forum.sysinternals.com/section-object_topic24975.html # For a better reference see the comments on the Section class # in mw_monitor_classes.py section_obj = handle.dereference_as("_SECTION_OBJECT") if (proc_obj or mapping_proc) and section_obj: break break # proc_obj represents the process over which the section is mapped # section_object represents the section being mapped. if (proc_obj is not None or mapping_proc is not None) and section_obj is not None: mapped_sec = None if not mapping_proc: for proc in mwmon.data.procs: if proc.pid == proc_obj.UniqueProcessId: mapping_proc = proc break if mapping_proc is None: mwmon.printer("[!] The mapping process is not being monitored," + " a handle was obtained with an API different from " + "OpenProcess or CreateProcess") return for sec in mwmon.data.sections: if sec.get_offset() == section_obj.obj_offset: mapped_sec = sec break # If the section was not in our list, we create an entry if mapped_sec is None: mapped_sec = Section(pgd, section_obj) mwmon.data.sections.append(mapped_sec) # Record the actual map once we return back from the call and we can # dereference output parameters callback_name = mwmon.cm.generate_callback_name("mapviewofsection_ret") # Arguments to callback: the callback name, so that it can unset it, # the process handle variable, and the section handle callback_function = functools.partial(ntmapviewofsection_ret, pid=pid, callback_name=callback_name, mapping_proc=mapping_proc, mapped_sec=mapped_sec, base_p=base_p, size_p=size_p, offset_p=offset_p, proc=proc, update_vads=update_vads) mwmon.cm.add_callback(CallbackManager.INSN_BEGIN_CB, callback_function, name=callback_name, addr=ret_addr, pgd=pgd)
def update_vads(self): ''' Call volatility to obtain VADS. ''' if self.__unpickled: return import volatility.obj as obj import volatility.win32.tasks as tasks import volatility.plugins.vadinfo as vadinfo from utils import get_addr_space addr_space = get_addr_space(self.get_pgd()) eprocs = [ t for t in tasks.pslist(addr_space) if t.UniqueProcessId == self.__pid ] for task in eprocs: heaps = task.Peb.ProcessHeaps.dereference() modules = [mod.DllBase for mod in task.get_load_modules()] stacks = [] for thread in task.ThreadListHead.list_of_type( "_ETHREAD", "ThreadListEntry"): teb = obj.Object("_TEB", offset=thread.Tcb.Teb, vm=task.get_process_address_space()) if teb: stacks.append(teb.NtTib.StackBase) for vad in task.VadRoot.traverse(): if vad is not None: vad_type = "" if vad.Start in heaps: # Heaps vad_type = "H" elif vad.Start in modules: # Module vad_type = "M" elif vad.Start in stacks: # Stacks vad_type = "S" else: vad_type = "-" try: protection = vadinfo.PROTECT_FLAGS.get( vad.VadFlags.Protection.v(), "") except Exception: traceback.print_exc() fileNameWithDevice = "" try: control_area = vad.ControlArea # even if the ControlArea is not NULL, it is only meaningful # for shared (non private) memory sections. if vad.VadFlags.PrivateMemory != 1 and control_area: if control_area: file_object = vad.FileObject if file_object: fileNameWithDevice = file_object.file_name_with_device( ) except AttributeError: pass try: new_vad = VADRegion(vad.Start, (vad.End - vad.Start), self, fileNameWithDevice, str(vad.Tag), vad_type, (vad.VadFlags.PrivateMemory == 1), protection) except Exception: traceback.print_exc() if new_vad not in self.__vads: self.__vads.append(new_vad)
def ntunmapviewofsection(params, pid, proc, update_vads): import volatility.win32.tasks as tasks from mw_monitor_classes import mwmon from utils import get_addr_space import api TARGET_LONG_SIZE = api.get_os_bits() / 8 cpu_index = params["cpu_index"] cpu = params["cpu"] # IN HANDLE ProcessHandle, # IN PVOID BaseAddress); # Search for the map, and deactivate it pgd = api.get_running_process(cpu_index) ret_addr, proc_handle, base = read_parameters(cpu, 2) # Load volatility address space addr_space = get_addr_space(pgd) # Get list of processes, and filter out by the process that triggered the # call (current process id) eprocs = [t for t in tasks.pslist(addr_space) if t.UniqueProcessId == pid] # Initialize proc_obj, that will point to the object of the referenced # process, and section_obj, idem proc_obj = None # Search handle table for the caller process for task in eprocs: if task.UniqueProcessId == pid: if (TARGET_LONG_SIZE == 4 and proc_handle == 0xffffffff) or \ (TARGET_LONG_SIZE == 8 and proc_handle == 0xffffffffffffffff): proc_obj = task break elif task.UniqueProcessId == pid and task.ObjectTable.HandleTableList: for handle in task.ObjectTable.handles(): if handle.is_valid(): if handle.HandleValue == proc_handle and handle.get_object_type() == "Process": proc_obj = handle.dereference_as("_EPROCESS") break break mapping_proc = None if proc_obj is not None: for proc in mwmon.data.procs: if proc.pid == proc_obj.UniqueProcessId: mapping_proc = proc break if mapping_proc is not None: for m in mapping_proc.section_maps: if m.base == base and m.is_active(): m.deactivate() if mwmon.interproc_text_log and mwmon.interproc_text_log_handle is not None: f = mwmon.interproc_text_log_handle if (TARGET_LONG_SIZE == 4): f.write("[PID: %x] NtUnmapViewOfSection: Base: %08x Size: %08x / Section: %s\n" % (pid, base, m.size, m.section.backing_file)) elif (TARGET_LONG_SIZE == 8): f.write("[PID: %x] NtUnmapViewOfSection: Base: %16x Size: %16x / Section: %s\n" % (pid, base, m.size, m.section.backing_file)) if update_vads: proc.update_vads()
def ntallocatevirtualmemory(params, pid, proc, update_vads): import volatility.win32.tasks as tasks from mw_monitor_classes import mwmon import api from api import CallbackManager from utils import get_addr_space TARGET_LONG_SIZE = api.get_os_bits() / 8 cpu_index = params["cpu_index"] cpu = params["cpu"] pgd = api.get_running_process(cpu_index) # IN HANDLE ProcessHandle, # IN OUT PVOID *BaseAddress, # IN ULONG ZeroBits, # IN OUT PULONG RegionSize, # IN ULONG AllocationType, # IN ULONG Protect ); # Only used for logging the event if not mwmon.interproc_text_log: return # Get call parameters (ret_addr, proc_handle, base_addr_p, zerobits, size_p, aloc_type, access) = read_parameters(cpu, 6) # Load volatility address space addr_space = get_addr_space(pgd) # Get list of processes, and filter out by the process that triggered the # call (current process id) eprocs = [t for t in tasks.pslist(addr_space) if t.UniqueProcessId == pid] # Initialize proc_obj, that will point to the object of the referenced # process, and section_obj, idem proc_obj = None # Search handle table for the caller process for task in eprocs: if task.UniqueProcessId == pid: if (TARGET_LONG_SIZE == 4 and proc_handle == 0xffffffff) or \ (TARGET_LONG_SIZE == 8 and proc_handle == 0xffffffffffffffff): proc_obj = task break elif task.UniqueProcessId == pid and task.ObjectTable.HandleTableList: for handle in task.ObjectTable.handles(): if handle.is_valid(): if handle.HandleValue == proc_handle and handle.get_object_type() == "Process": proc_obj = handle.dereference_as("_EPROCESS") break break mapping_proc = None if proc_obj is not None: for proc in mwmon.data.procs: if proc.pid == proc_obj.UniqueProcessId: mapping_proc = proc break if mapping_proc is not None: # Arguments to callback: the callback name, so that it can unset it, # the process handle variable, and the section handle callback_name = mwmon.cm.generate_callback_name( "ntallocatevirtualmemory_ret") callback_function = functools.partial(ntallocatevirtualmemory_ret, pid=pid, callback_name=callback_name, mapping_proc=mapping_proc, base_addr_p=base_addr_p, zerobits=zerobits, size_p=size_p, aloc_type=aloc_type, access=access, proc=proc, update_vads=update_vads) mwmon.cm.add_callback(CallbackManager.INSN_BEGIN_CB, callback_function, name=callback_name, addr=ret_addr, pgd=pgd)
def windows_insert_module_internal(p_pid, p_pgd, base, size, fullname, basename, checksum, nt_header, update_symbols): from utils import get_addr_space from vmi import modules from vmi import symbols from vmi import Module from vmi import PseudoLDRDATA from api_internal import dispatch_module_load_callback from api_internal import dispatch_module_remove_callback mod = Module(base, size, p_pid, p_pgd, checksum, basename, fullname) if p_pgd != 0: addr_space = get_addr_space(p_pgd) else: addr_space = get_addr_space() # First, we try to get the symbols from the cache if (checksum, fullname) in symbols: mod.set_symbols(symbols[(checksum, fullname)]) # If we are updating symbols (a simple module retrieval would # not require symbol extraction), and, either we dont have any # symbols on the cache, or we have an empty list (symbols could # not be retrieved for some reason, such as a missing memory page, # we try symbol resolution if update_symbols and ((checksum, fullname) not in symbols or len(symbols[(checksum, fullname)]) == 0): syms = {} export_dir = nt_header.OptionalHeader.DataDirectory[0] if export_dir: expdir = obj.Object('_IMAGE_EXPORT_DIRECTORY', offset=base + export_dir.VirtualAddress, vm=addr_space, parent=PseudoLDRDATA(base, basename, export_dir)) if expdir.valid(nt_header): # Ordinal, Function RVA, and Name Object for o, f, n in expdir._exported_functions(): if not isinstance(o, obj.NoneObject) and \ not isinstance(f, obj.NoneObject) and \ not isinstance(n, obj.NoneObject): syms[str(n)] = f.v() # If we managed to parse export table, update symbols, # no matter if it is empty if (checksum, fullname) not in symbols or len(syms) > len( symbols[(checksum, fullname)]): symbols[(checksum, fullname)] = syms # Even if it is empty, the module symbols are set # to an empty list, and thus are 'resolved'. # Anyway, in future updates, they could be resolved, # as we allow this in the first condition. mod.set_symbols(symbols[(checksum, fullname)]) # Add module to mods_pending, or # Update modules that may we might not have been # able to resolve symbols in the past if len(syms) == 0: if (checksum, fullname) not in mods_pending: mods_pending[(checksum, fullname)] = [] mods_pending[(checksum, fullname)].append(mod) else: if (checksum, fullname) in mods_pending and len( mods_pending[(checksum, fullname)]) > 0: for mod in mods_pending[(checksum, fullname)]: mod.set_symbols(syms) del mods_pending[(checksum, fullname)] else: # Since there is no export dir, we assume that it does # not have any symbols if (checksum, fullname) not in symbols: symbols[(checksum, fullname)] = [] # Even if it is empty, the module symbols are set # to an empty list, and thus are 'resolved'. # Anyway, in future updates, they could be resolved, # as we allow this in the first condition. mod.set_symbols(symbols[(checksum, fullname)]) # Add module to mods_pending, or if (checksum, fullname) not in mods_pending: mods_pending[(checksum, fullname)] = [] mods_pending[(checksum, fullname)].append(mod) #Module load/del notification if base in modules[(p_pid, p_pgd)]: if modules[(p_pid, p_pgd)][base].get_size() != size or \ modules[(p_pid, p_pgd)][base].get_checksum() != checksum or \ modules[(p_pid, p_pgd)][base].get_name() != basename or \ modules[(p_pid, p_pgd)][base].get_fullname() != fullname: # Notify of module deletion and module load dispatch_module_remove_callback( p_pid, p_pgd, base, modules[(p_pid, p_pgd)][base].get_size(), modules[(p_pid, p_pgd)][base].get_name(), modules[(p_pid, p_pgd)][base].get_fullname()) del modules[(p_pid, p_pgd)][base] modules[(p_pid, p_pgd)][base] = mod dispatch_module_load_callback(p_pid, p_pgd, base, size, basename, fullname) # If we updated the symbols and have a bigger list now, dont substitute the module # but update its symbols instead elif len(mod.get_symbols()) > len( modules[(p_pid, p_pgd)][base].get_symbols()): modules[(p_pid, p_pgd)][base].set_symbols(mod.get_symbols()) else: # Just notify of module load modules[(p_pid, p_pgd)][base] = mod dispatch_module_load_callback(p_pid, p_pgd, base, size, basename, fullname) # Mark the module as present modules[(p_pid, p_pgd)][base].set_present()
def ntvirtualprotect(params, pid, proc, update_vads): import volatility.win32.tasks as tasks from mw_monitor_classes import mwmon from utils import get_addr_space import api TARGET_LONG_SIZE = api.get_os_bits() / 8 cpu_index = params["cpu_index"] cpu = params["cpu"] pgd = api.get_running_process(cpu_index) # IN HANDLE ProcessHandle, # IN OUT PVOID *BaseAddress, # IN OUT PULONG NumberOfBytesToProtect, # IN ULONG NewAccessProtection, # OUT PULONG OldAccessProtection ); # Keep a log of page permissions for each VAD. Log changes for every virtualprotect call. # Output this informaiton on the log file, signal the sections with # changed permissions. ret_addr, proc_handle, base_addr_p, size_p, new_access, old_access = read_parameters( cpu, 5) # Load volatility address space addr_space = get_addr_space(pgd) # Get call parameters base_addr = dereference_target_long(base_addr_p, pgd) size = dereference_target_long(size_p, pgd) # Get list of processes, and filter out by the process that triggered the # call (current process id) eprocs = [t for t in tasks.pslist(addr_space) if t.UniqueProcessId == pid] # Initialize proc_obj, that will point to the object of the referenced # process, and section_obj, idem proc_obj = None # Search handle table for the caller process for task in eprocs: if task.UniqueProcessId == pid: if (TARGET_LONG_SIZE == 4 and proc_handle == 0xffffffff) or \ (TARGET_LONG_SIZE == 8 and proc_handle == 0xffffffffffffffff): proc_obj = task break elif task.UniqueProcessId == pid and task.ObjectTable.HandleTableList: for handle in task.ObjectTable.handles(): if handle.is_valid(): if handle.HandleValue == proc_handle and handle.get_object_type() == "Process": proc_obj = handle.dereference_as("_EPROCESS") break break mapping_proc = None if proc_obj is not None: for proc in mwmon.data.procs: if proc.pid == proc_obj.UniqueProcessId: mapping_proc = proc break if mapping_proc is not None: for v in mapping_proc.vads: # If the block overlaps the vad: if base_addr >= v.start and base_addr < (v.start + v.size): v.update_page_access(base_addr, size, new_access) if mwmon.interproc_text_log and mwmon.interproc_text_log_handle is not None: f = mwmon.interproc_text_log_handle if TARGET_LONG_SIZE == 4: f.write("[PID: %x] NtVirtualProtect: Base: %08x Size: %08x NewProtect: %08x\n" % (pid, base_addr, size, new_access)) elif TARGET_LONG_SIZE == 8: f.write("[PID: %x] NtVirtualProtect: Base: %016x Size: %016x NewProtect: %016x\n" % (pid, base_addr, size, new_access)) if update_vads: proc.update_vads()
def windows_update_modules(pgd, update_symbols=False): ''' Use volatility to get the modules and symbols for a given process, and update the cache accordingly ''' global last_kdbg global symbol_cache_must_be_saved import api from utils import get_addr_space from vmi import set_modules_non_present from vmi import clean_non_present_modules from vmi import add_module from vmi import get_module from vmi import has_module if pgd != 0: addr_space = get_addr_space(pgd) else: addr_space = get_addr_space() if addr_space is None: pp_error("Volatility address space not loaded\n") return [] # Get EPROC directly from its offset procs = api.get_process_list() inserted_bases = [] # Parse/update kernel modules if pgd 0 is requested: if pgd == 0 and last_kdbg is not None: kdbg = obj.Object( "_KDDEBUGGER_DATA64", offset=last_kdbg, vm=addr_space) # List entries are returned, so that # we can monitor memory writes to these # entries and detect when a module is added # or removed list_entry_size = None list_entry_regions = [] # Add the initial list pointer as a list entry list_entry_regions.append((kdbg.obj_offset, kdbg.PsLoadedModuleList.obj_offset, kdbg.PsLoadedModuleList.size())) # Mark all modules as non-present set_modules_non_present(0, 0) for module in kdbg.modules(): if module.DllBase not in inserted_bases: inserted_bases.append(module.DllBase) windows_insert_module(0, 0, module, update_symbols) if list_entry_size is None: list_entry_size = module.InLoadOrderLinks.size() list_entry_regions.append((module.obj_offset, module.InLoadOrderLinks.obj_offset, list_entry_size * 3)) # Remove all the modules that are not marked as present clean_non_present_modules(0, 0) if symbol_cache_must_be_saved: from vmi import save_symbols_to_cache_file save_symbols_to_cache_file() symbol_cache_must_be_saved = False return list_entry_regions for proc in procs: p_pid = proc["pid"] p_pgd = proc["pgd"] # p_name = proc["name"] p_kernel_addr = proc["kaddr"] if p_pgd == pgd: task = obj.Object("_EPROCESS", offset=p_kernel_addr, vm=addr_space) # List entries are returned, so that # we can monitor memory writes to these # entries and detect when a module is added # or removed list_entry_size = None list_entry_regions = [] scan_peb = True if task.Peb is None or not task.Peb.is_valid(): if isinstance(task.Peb.obj_offset, int): list_entry_regions.append((task.obj_offset, task.Peb.obj_offset, task.Peb.size())) scan_peb = False if task.Peb.Ldr is None or not task.Peb.Ldr.is_valid(): list_entry_regions.append((task.Peb.v(), task.Peb.Ldr.obj_offset, task.Peb.Ldr.size())) scan_peb = False if scan_peb: # Add the initial list pointer as a list entry if we already have a PEB and LDR list_entry_regions.append((task.Peb.Ldr.dereference().obj_offset, task.Peb.Ldr.InLoadOrderModuleList.obj_offset, task.Peb.Ldr.InLoadOrderModuleList.size() * 3)) # Note: we do not erase the modules we have information for from the list, # unless we have a different module loaded at the same base address. # In this way, if at some point the module gets unmapped from the PEB list # but it is still in memory, we do not loose the information. # Mark all modules as non-present set_modules_non_present(p_pid, p_pgd) for module in task.get_init_modules(): if module.DllBase not in inserted_bases: inserted_bases.append(module.DllBase) windows_insert_module(p_pid, p_pgd, module, update_symbols) if list_entry_size is None: list_entry_size = module.InLoadOrderLinks.size() list_entry_regions.append((module.obj_offset, module.InLoadOrderLinks.obj_offset, list_entry_size * 3)) for module in task.get_mem_modules(): if module.DllBase not in inserted_bases: inserted_bases.append(module.DllBase) windows_insert_module(p_pid, p_pgd, module, update_symbols) if list_entry_size is None: list_entry_size = module.InLoadOrderLinks.size() list_entry_regions.append((module.obj_offset, module.InLoadOrderLinks.obj_offset, list_entry_size * 3)) for module in task.get_load_modules(): if module.DllBase not in inserted_bases: inserted_bases.append(module.DllBase) windows_insert_module(p_pid, p_pgd, module, update_symbols) if list_entry_size is None: list_entry_size = module.InLoadOrderLinks.size() list_entry_regions.append((module.obj_offset, module.InLoadOrderLinks.obj_offset, list_entry_size * 3)) # Now, if we are a 64bit system and the process is a Wow64 process, traverse VAD # to find the 32 bit modules if api.get_os_bits() == 64 and task.IsWow64: for vad in task.VadRoot.traverse(): if vad is not None: if hasattr(vad, "FileObject"): f = vad.FileObject if f is not None: fname = f.file_name_with_device() if fname and "Windows\\SysWOW64".lower() in fname.lower() and ".dll" == fname[-4:].lower(): fname_starts = fname.find("Windows\\SysWOW64") fname = fname[fname_starts:] windows_insert_module_internal(p_pid, p_pgd, vad.Start, vad.End - vad.Start, fname, fname.split("\\")[-1], "", update_symbols, do_stop = True) # Remove all the modules that are not marked as present clean_non_present_modules(p_pid, p_pgd) if symbol_cache_must_be_saved: from vmi import save_symbols_to_cache_file save_symbols_to_cache_file() symbol_cache_must_be_saved = False return list_entry_regions return None
def ntwritevirtualmemory(params, pid, proc, update_vads, reverse=False): import volatility.win32.tasks as tasks from mw_monitor_classes import mwmon from mw_monitor_classes import Injection import api from utils import get_addr_space TARGET_LONG_SIZE = api.get_os_bits() / 8 cpu_index = params["cpu_index"] cpu = params["cpu"] # _In_ HANDLE ProcessHandle, # _In_ PVOID BaseAddress, # _In_ PVOID Buffer, # _In_ SIZE_T NumberOfBytesToWrite, # _Out_opt_ PSIZE_T NumberOfBytesWritten pgd = api.get_running_process(cpu_index) # Read the parameters ret_addr, proc_hdl, remote_addr, local_addr, size, size_out = read_parameters( cpu, 5) local_proc = None # Get local proc for proc in mwmon.data.procs: if proc.pid == pid: local_proc = proc break # Try to get remote process from list of monitored processes remote_proc = None # Load volatility address space addr_space = get_addr_space(pgd) # Get list of processes, and filter out by the process that triggered the # call (current process id) eprocs = [t for t in tasks.pslist(addr_space) if t.UniqueProcessId == pid] # Initialize proc_obj, that will point to the eprocess of the new created # process proc_obj = None # Search handle table for the new created process for task in eprocs: if task.UniqueProcessId == pid and task.ObjectTable.HandleTableList: for handle in task.ObjectTable.handles(): if handle.is_valid( ) and handle.HandleValue == proc_hdl and handle.get_object_type( ) == "Process": proc_obj = handle.dereference_as("_EPROCESS") break break if proc_obj is not None: for proc in mwmon.data.procs: if proc.pid == proc_obj.UniqueProcessId: remote_proc = proc break else: # Sometimes we get calls to this function over non-proc handles (e.g. type "Desktop") return if remote_proc is None: mwmon.printer( "[!] Could not obtain remote proc, or it is not being monitored") return elif local_proc is None: mwmon.printer( "[!] Could not obtain local proc, or it is not being monitored") return else: if reverse: data = None if mwmon.interproc_text_log and mwmon.interproc_text_log_handle is not None: f = mwmon.interproc_text_log_handle if TARGET_LONG_SIZE == 4: f.write( "[PID: %x] NtReadVirtualMemory: PID: %x - Addr: %08x <-- PID: %x Addr: %08x / Size: %08x\n" % (pid, local_proc.pid, local_addr, remote_proc.pid, remote_addr, size)) elif TARGET_LONG_SIZE == 8: f.write( "[PID: %x] NtReadVirtualMemory: PID: %x - Addr: %16x <-- PID: %x Addr: %16x / Size: %16x\n" % (pid, local_proc.pid, local_addr, remote_proc.pid, remote_addr, size)) else: data = None if mwmon.interproc_text_log and mwmon.interproc_text_log_handle is not None: f = mwmon.interproc_text_log_handle if TARGET_LONG_SIZE == 4: f.write( "[PID: %x] NtWriteVirtualMemory: PID: %x - Addr: %08x --> PID: %x Addr: %08x / Size: %08x\n" % (pid, local_proc.pid, local_addr, remote_proc.pid, remote_addr, size)) elif TARGET_LONG_SIZE == 8: f.write( "[PID: %x] NtWriteVirtualMemory: PID: %x - Addr: %16x --> PID: %x Addr: %16x / Size: %16x\n" % (pid, local_proc.pid, local_addr, remote_proc.pid, remote_addr, size)) inj = Injection(remote_proc, remote_addr, local_proc, local_addr, size, data, reverse) local_proc.add_injection(inj) if update_vads: proc.update_vads()
def windows_insert_module_internal(p_pid, p_pgd, base, size, fullname, basename, checksum, nt_header, update_symbols): from utils import get_addr_space from vmi import modules from vmi import symbols from vmi import Module from vmi import PseudoLDRDATA from api_internal import dispatch_module_load_callback from api_internal import dispatch_module_remove_callback mod = Module(base, size, p_pid, p_pgd, checksum, basename, fullname) if p_pgd != 0: addr_space = get_addr_space(p_pgd) else: addr_space = get_addr_space() # Getting symbols, from cache! if (checksum, fullname) in symbols: mod.set_symbols(symbols[(checksum, fullname)]) elif update_symbols: syms = {} export_dir = nt_header.OptionalHeader.DataDirectory[0] if export_dir: expdir = obj.Object('_IMAGE_EXPORT_DIRECTORY', offset=base + export_dir.VirtualAddress, vm=addr_space, parent=PseudoLDRDATA(base, basename, export_dir)) if expdir.valid(nt_header): # Ordinal, Function RVA, and Name Object for o, f, n in expdir._exported_functions(): if not isinstance(o, obj.NoneObject) and \ not isinstance(f, obj.NoneObject) and \ not isinstance(n, obj.NoneObject): syms[str(n)] = f.v() if len(syms) > 0: symbols[(checksum, fullname)] = syms mod.set_symbols(syms) if (checksum, fullname) in mods_pending: for m in mods_pending[(checksum, fullname)]: m.set_symbols(syms) del mods_pending[(checksum, fullname)] else: if (checksum, fullname) in mods_pending: mods_pending[(checksum, fullname)].append(mod) else: mods_pending[(checksum, fullname)] = [mod] #Module load/del notification if base in modules[(p_pid, p_pgd)]: if modules[(p_pid, p_pgd)][base].get_size() != size or \ modules[(p_pid, p_pgd)][base].get_checksum() != checksum or \ modules[(p_pid, p_pgd)][base].get_name() != basename or \ modules[(p_pid, p_pgd)][base].get_fullname() != fullname: # Notify of module deletion and module load dispatch_module_remove_callback( p_pid, p_pgd, base, modules[(p_pid, p_pgd)][base].get_size(), modules[(p_pid, p_pgd)][base].get_name(), modules[(p_pid, p_pgd)][base].get_fullname()) del modules[(p_pid, p_pgd)][base] dispatch_module_load_callback(p_pid, p_pgd, base, size, basename, fullname) modules[(p_pid, p_pgd)][base] = mod else: # Just notify of module load dispatch_module_load_callback(p_pid, p_pgd, base, size, basename, fullname) modules[(p_pid, p_pgd)][base] = mod # Mark the module as present modules[(p_pid, p_pgd)][base].set_present()
def ntreadfile(params, pid, proc, update_vads, is_write=False): import volatility.win32.tasks as tasks from mw_monitor_classes import mwmon from mw_monitor_classes import FileRead from mw_monitor_classes import FileWrite from mw_monitor_classes import File from utils import get_addr_space import api TARGET_LONG_SIZE = api.get_os_bits() / 8 cpu_index = params["cpu_index"] cpu = params["cpu"] # IN HANDLE FileHandle, # IN HANDLE Event OPTIONAL, # IN PIO_APC_ROUTINE ApcRoutine OPTIONAL, # IN PVOID ApcContext OPTIONAL, # OUT PIO_STATUS_BLOCK IoStatusBlock, # OUT PVOID Buffer, # IN ULONG Length, # IN PLARGE_INTEGER ByteOffset OPTIONAL, # IN PULONG Key OPTIONAL pgd = api.get_running_process(cpu_index) # Read the parameters ret_addr, file_handle, arg2, arg3, arg4, arg5, buff, length, offset_p, arg9 = read_parameters( cpu, 9) # Load volatility address space addr_space = get_addr_space(pgd) # Get list of processes, and filter out by the process that triggered the # call (current process id) eprocs = [t for t in tasks.pslist(addr_space) if t.UniqueProcessId == pid] # Initialize file_obj, that will point to the object of the referenced file file_obj = None # Search handle table for the new created process for task in eprocs: if task.UniqueProcessId == pid and task.ObjectTable.HandleTableList: for handle in task.ObjectTable.handles(): if handle.is_valid( ) and handle.HandleValue == file_handle and handle.get_object_type( ) == "File": file_obj = handle.dereference_as("_FILE_OBJECT") break break if file_obj is not None: file_instance = None for fi in mwmon.data.files: if fi.file_name == str(file_obj.FileName): file_instance = fi break # If we have still not recorded the file, add it to files to record if file_instance is None: file_instance = File(str(file_obj.FileName)) mwmon.data.files.append(file_instance) # Now, record the read/write # curr_file_offset is never used # curr_file_offset = int(file_obj.CurrentByteOffset.QuadPart) # FO_SYNCHRONOUS_IO 0x0000002 is_offset_maintained = ((file_obj.Flags & 0x0000002) != 0) # If no offset was specified, and the offset is mantained, the real # offset is taken from the file object offset = None if offset_p == 0 and is_offset_maintained: offset = int(file_obj.CurrentByteOffset.QuadPart) elif offset_p != 0: # If an offset is provided, the current offset in the file_object # will be updated, regardless of the flag. try: offset = struct.unpack("Q", api.r_va(pgd, offset_p, 8))[0] except: offset = 0 mwmon.printer( "Could not dereference offset in NtReadFile call in interproc.py" ) else: # If no offset was specified and the file object does not have the flag set, we may be in front of some kind # of corruption error or deliberate manipulation print "[!] The file object flag FO_SYNCHRONOUS_IO is not set, and no offset was provided" return # At this moment we do not record the data op = None for proc in mwmon.data.procs: if proc.pid == pid: local_proc = proc break if not is_write: op = FileRead(file_instance, local_proc, offset, length, None) if mwmon.interproc_text_log and mwmon.interproc_text_log_handle is not None: f = mwmon.interproc_text_log_handle if TARGET_LONG_SIZE == 4: f.write( "[PID: %x] NtReadFile: Offset: %08x Size: %08x / %s\n" % (pid, offset, length, str(file_obj.FileName))) elif TARGET_LONG_SIZE == 8: f.write( "[PID: %x] NtReadFile: Offset: %16x Size: %16x / %s\n" % (pid, offset, length, str(file_obj.FileName))) else: op = FileWrite(file_instance, local_proc, offset, length, None) if mwmon.interproc_text_log and mwmon.interproc_text_log_handle is not None: f = mwmon.interproc_text_log_handle if TARGET_LONG_SIZE == 4: f.write( "[PID: %x] NtWriteFile: Offset: %08x Size: %08x / %s\n" % (pid, offset, length, str(file_obj.FileName))) elif TARGET_LONG_SIZE == 8: f.write( "[PID: %x] NtWriteFile: Offset: %16x Size: %16x / %s\n" % (pid, offset, length, str(file_obj.FileName))) file_instance.add_operation(op) local_proc.file_operations.append(op) if update_vads: proc.update_vads()
def dump(params, pid=None, proc=None, update_vads=None, from_addr=None, callback_name=None, terminate_process=False): ''' Dump the process, modules, vads... ''' import volatility.constants as constants import volatility.exceptions as exceptions import volatility.obj as obj import volatility.win32.tasks as tasks from mw_monitor_classes import mwmon from utils import get_addr_space import api TARGET_LONG_SIZE = api.get_os_bits() / 8 cpu_index = params["cpu_index"] cpu = params["cpu"] mwmon.printer("Dumping process...") proc_hdl = None if terminate_process: if TARGET_LONG_SIZE == 4: # ret, proc_hdl, exit_status try: _, proc_hdl, _ = struct.unpack( "<III", api.r_va(api.get_running_process(cpu_index), cpu.ESP, 4 * 3)) except: proc_hdl = 0 mwmon.printer( "Could not dereference process handle in dumper.py") elif TARGET_LONG_SIZE == 8: # We don't need the return address # ret = struct.unpack("<Q", # api.r_va(api.get_running_process(cpu_index), cpu.ESP, 8))[0] proc_hdl = cpu.RCX # We don't need the exit status # exit_status = cpu.RDX # It seems there are usually 2 calls, when a process terminates itself. # First, ZwTerminateProcess is called with 0 as proc_hdl, and afterwards # -1. if proc_hdl == 0: return if callback_name is not None: # First, remove callback mwmon.cm.rm_callback(callback_name) # Check if we have been called from the right from_addr if from_addr is not None: if TARGET_LONG_SIZE == 4: try: buff = api.r_va(api.get_running_process(cpu_index), cpu.ESP, 4) ret_addr = struct.unpack("<I", buff)[0] except: ret_addr = 0 mwmon.printer( "Could not dereference return address on dumper.py") elif TARGET_LONG_SIZE == 8: try: buff = api.r_va(api.get_running_process(cpu_index), cpu.RSP, 8) ret_addr = struct.unpack("<Q", buff)[0] except: ret_addr = 0 mwmon.printer( "Could not dereference return address on dumper.py") if from_addr != ret_addr: return # We have been called from the right point, now, dump. path = mwmon.dumper_path # Dump a file with the VAD info, etc, and a filename for each dumped file, # so that we can import feed IDA with it try: # Dump main executable. addr_space = get_addr_space() # If 1 handle is specified, get the pid for that handle instead # of the calling PID. if proc_hdl is not None: if (TARGET_LONG_SIZE == 4 and proc_hdl == 0xFFFFFFFF) or \ (TARGET_LONG_SIZE == 8 and proc_hdl == 0xFFFFFFFFFFFFFFFF): # If the handle is 0xFFFFFFFF, then the process is the caller. pass else: eprocs = [ t for t in tasks.pslist(addr_space) if t.UniqueProcessId == pid ] proc_obj = None # Search handle table for the new created process for task in eprocs: if task.UniqueProcessId == pid and task.ObjectTable.HandleTableList: for handle in task.ObjectTable.handles(): if handle.is_valid( ) and handle.HandleValue == proc_hdl and handle.get_object_type( ) == "Process": proc_obj = handle.dereference_as("_EPROCESS") break break if proc_obj is not None: # If we found the handle to which it referred, update the # corresponding pid pid = int(proc_obj.UniqueProcessId) else: return # Case when no PID is specified, just dump everything if pid is None: pids = [p.pid for p in mwmon.data.procs] # When one pid is specified, dump that PID else: pids = [pid] eprocs = [ t for t in tasks.pslist(addr_space) if t.UniqueProcessId in pids ] for task in eprocs: mwmon.printer("Dumping process %x" % (task.UniqueProcessId)) # Code adapted from procdump (volatility) task_space = task.get_process_address_space() if task_space is None: mwmon.printer("Error: Cannot acquire process AS") return elif task.Peb is None: # we must use m() here, because any other attempt to # reference task.Peb will try to instantiate the _PEB mwmon.printer( "Error: PEB at {0:#x} is unavailable (possibly due to paging)" .format(task.m('Peb'))) return elif task_space.vtop(task.Peb.ImageBaseAddress) is None: mwmon.printer( "Error: ImageBaseAddress at {0:#x} is unavailable" + "(possibly due to paging)".format( task.Peb.ImageBaseAddress)) return else: mwmon.printer("Dumping executable for %x" % (task.UniqueProcessId)) dump_file = os.path.join( path, "executable.%x.exe" % (task.UniqueProcessId)) of = open(dump_file, 'wb') pe_file = obj.Object("_IMAGE_DOS_HEADER", offset=task.Peb.ImageBaseAddress, vm=task_space) try: for offset, code in pe_file.get_image(unsafe=True, memory=False, fix=True): of.seek(offset) of.write(code) except ValueError, ve: mwmon.printer("Error: {0}".format(ve)) return except exceptions.SanityCheckException, ve: mwmon.printer("Error: {0} Try -u/--unsafe".format(ve)) return
def ntunmapviewofsection(params, pid, proc, update_vads): import volatility.win32.tasks as tasks from mw_monitor_classes import mwmon from utils import get_addr_space import api TARGET_LONG_SIZE = api.get_os_bits() / 8 cpu_index = params["cpu_index"] cpu = params["cpu"] # IN HANDLE ProcessHandle, # IN PVOID BaseAddress); # Search for the map, and deactivate it pgd = api.get_running_process(cpu_index) ret_addr, proc_handle, base = read_parameters(cpu, 2) # Load volatility address space addr_space = get_addr_space(pgd) # Get list of processes, and filter out by the process that triggered the # call (current process id) eprocs = [t for t in tasks.pslist(addr_space) if t.UniqueProcessId == pid] # Initialize proc_obj, that will point to the object of the referenced # process, and section_obj, idem proc_obj = None # Search handle table for the caller process for task in eprocs: if task.UniqueProcessId == pid: if (TARGET_LONG_SIZE == 4 and proc_handle == 0xffffffff) or \ (TARGET_LONG_SIZE == 8 and proc_handle == 0xffffffffffffffff): proc_obj = task break elif task.UniqueProcessId == pid and task.ObjectTable.HandleTableList: for handle in task.ObjectTable.handles(): if handle.is_valid(): if handle.HandleValue == proc_handle and handle.get_object_type( ) == "Process": proc_obj = handle.dereference_as("_EPROCESS") break break mapping_proc = None if proc_obj is not None: for proc in mwmon.data.procs: if proc.pid == proc_obj.UniqueProcessId: mapping_proc = proc break if mapping_proc is not None: for m in mapping_proc.section_maps: if m.base == base and m.is_active(): m.deactivate() if mwmon.interproc_text_log and mwmon.interproc_text_log_handle is not None: f = mwmon.interproc_text_log_handle if (TARGET_LONG_SIZE == 4): f.write( "[PID: %x] NtUnmapViewOfSection: Base: %08x Size: %08x / Section: %s\n" % (pid, base, m.size, m.section.backing_file)) elif (TARGET_LONG_SIZE == 8): f.write( "[PID: %x] NtUnmapViewOfSection: Base: %16x Size: %16x / Section: %s\n" % (pid, base, m.size, m.section.backing_file)) if update_vads: proc.update_vads()
def ntreadfile(params, pid, proc, update_vads, is_write=False): import volatility.win32.tasks as tasks from mw_monitor_classes import mwmon from mw_monitor_classes import FileRead from mw_monitor_classes import FileWrite from mw_monitor_classes import File from utils import get_addr_space import api TARGET_LONG_SIZE = api.get_os_bits() / 8 cpu_index = params["cpu_index"] cpu = params["cpu"] # IN HANDLE FileHandle, # IN HANDLE Event OPTIONAL, # IN PIO_APC_ROUTINE ApcRoutine OPTIONAL, # IN PVOID ApcContext OPTIONAL, # OUT PIO_STATUS_BLOCK IoStatusBlock, # OUT PVOID Buffer, # IN ULONG Length, # IN PLARGE_INTEGER ByteOffset OPTIONAL, # IN PULONG Key OPTIONAL pgd = api.get_running_process(cpu_index) # Read the parameters ret_addr, file_handle, arg2, arg3, arg4, arg5, buff, length, offset_p, arg9 = read_parameters( cpu, 9) # Load volatility address space addr_space = get_addr_space(pgd) # Get list of processes, and filter out by the process that triggered the # call (current process id) eprocs = [t for t in tasks.pslist(addr_space) if t.UniqueProcessId == pid] # Initialize file_obj, that will point to the object of the referenced file file_obj = None # Search handle table for the new created process for task in eprocs: if task.UniqueProcessId == pid and task.ObjectTable.HandleTableList: for handle in task.ObjectTable.handles(): if handle.is_valid() and handle.HandleValue == file_handle and handle.get_object_type() == "File": file_obj = handle.dereference_as("_FILE_OBJECT") break break if file_obj is not None: file_instance = None for fi in mwmon.data.files: if fi.file_name == str(file_obj.FileName): file_instance = fi break # If we have still not recorded the file, add it to files to record if file_instance is None: file_instance = File(str(file_obj.FileName)) mwmon.data.files.append(file_instance) # Now, record the read/write # curr_file_offset is never used # curr_file_offset = int(file_obj.CurrentByteOffset.QuadPart) # FO_SYNCHRONOUS_IO 0x0000002 is_offset_maintained = ((file_obj.Flags & 0x0000002) != 0) # If no offset was specified, and the offset is mantained, the real # offset is taken from the file object offset = None if offset_p == 0 and is_offset_maintained: offset = int(file_obj.CurrentByteOffset.QuadPart) elif offset_p != 0: # If an offset is provided, the current offset in the file_object # will be updated, regardless of the flag. try: offset = struct.unpack("Q", api.r_va(pgd, offset_p, 8))[0] except: offset = 0 mwmon.printer("Could not dereference offset in NtReadFile call in interproc.py") else: # If no offset was specified and the file object does not have the flag set, we may be in front of some kind # of corruption error or deliberate manipulation print "[!] The file object flag FO_SYNCHRONOUS_IO is not set, and no offset was provided" return # At this moment we do not record the data op = None for proc in mwmon.data.procs: if proc.pid == pid: local_proc = proc break if not is_write: op = FileRead(file_instance, local_proc, offset, length, None) if mwmon.interproc_text_log and mwmon.interproc_text_log_handle is not None: f = mwmon.interproc_text_log_handle if TARGET_LONG_SIZE == 4: f.write("[PID: %x] NtReadFile: Offset: %08x Size: %08x / %s\n" % (pid, offset, length, str(file_obj.FileName))) elif TARGET_LONG_SIZE == 8: f.write("[PID: %x] NtReadFile: Offset: %16x Size: %16x / %s\n" % (pid, offset, length, str(file_obj.FileName))) else: op = FileWrite(file_instance, local_proc, offset, length, None) if mwmon.interproc_text_log and mwmon.interproc_text_log_handle is not None: f = mwmon.interproc_text_log_handle if TARGET_LONG_SIZE == 4: f.write("[PID: %x] NtWriteFile: Offset: %08x Size: %08x / %s\n" % (pid, offset, length, str(file_obj.FileName))) elif TARGET_LONG_SIZE == 8: f.write("[PID: %x] NtWriteFile: Offset: %16x Size: %16x / %s\n" % (pid, offset, length, str(file_obj.FileName))) file_instance.add_operation(op) local_proc.file_operations.append(op) if update_vads: proc.update_vads()
def windows_update_modules(pgd, update_symbols=False): ''' Use volatility to get the modules and symbols for a given process, and update the cache accordingly ''' global last_kdbg import api from utils import get_addr_space from vmi import modules from vmi import set_modules_non_present from vmi import clean_non_present_modules if pgd != 0: addr_space = get_addr_space(pgd) else: addr_space = get_addr_space() if addr_space is None: pp_error("Volatility address space not loaded\n") return [] # Get EPROC directly from its offset procs = api.get_process_list() inserted_bases = [] # Parse/update kernel modules if pgd 0 is requested: if pgd == 0 and last_kdbg is not None: if (0, 0) not in modules: modules[(0, 0)] = {} kdbg = obj.Object("_KDDEBUGGER_DATA64", offset=last_kdbg, vm=addr_space) # List entries are returned, so that # we can monitor memory writes to these # entries and detect when a module is added # or removed list_entry_size = None list_entry_regions = [] # Add the initial list pointer as a list entry list_entry_regions.append( (kdbg.obj_offset, kdbg.PsLoadedModuleList.obj_offset, kdbg.PsLoadedModuleList.size())) # Mark all modules as non-present set_modules_non_present(0, 0) for module in kdbg.modules(): if module.DllBase not in inserted_bases: inserted_bases.append(module.DllBase) windows_insert_module(0, 0, module, update_symbols) if list_entry_size is None: list_entry_size = module.InLoadOrderLinks.size() list_entry_regions.append( (module.obj_offset, module.InLoadOrderLinks.obj_offset, list_entry_size * 3)) # Remove all the modules that are not marked as present clean_non_present_modules(0, 0) return list_entry_regions for proc in procs: p_pid = proc["pid"] p_pgd = proc["pgd"] # p_name = proc["name"] p_kernel_addr = proc["kaddr"] if p_pgd == pgd: task = obj.Object("_EPROCESS", offset=p_kernel_addr, vm=addr_space) # List entries are returned, so that # we can monitor memory writes to these # entries and detect when a module is added # or removed list_entry_size = None list_entry_regions = [] if task.Peb is None or not task.Peb.is_valid(): if isinstance(task.Peb.obj_offset, int): list_entry_regions.append( (task.obj_offset, task.Peb.obj_offset, task.Peb.size())) return list_entry_regions if task.Peb.Ldr is None or not task.Peb.Ldr.is_valid(): list_entry_regions.append( (task.Peb.v(), task.Peb.Ldr.obj_offset, task.Peb.Ldr.size())) return list_entry_regions # Add the initial list pointer as a list entry if we already have a PEB and LDR list_entry_regions.append( (task.Peb.Ldr.dereference().obj_offset, task.Peb.Ldr.InLoadOrderModuleList.obj_offset, task.Peb.Ldr.InLoadOrderModuleList.size() * 3)) # Note: we do not erase the modules we have information for from the list, # unless we have a different module loaded at the same base address. # In this way, if at some point the module gets unmapped from the PEB list # but it is still in memory, we do not loose the information. if (p_pid, p_pgd) not in modules: modules[(p_pid, p_pgd)] = {} # Mark all modules as non-present set_modules_non_present(p_pid, p_pgd) for module in task.get_init_modules(): if module.DllBase not in inserted_bases: inserted_bases.append(module.DllBase) windows_insert_module(p_pid, p_pgd, module, update_symbols) if list_entry_size is None: list_entry_size = module.InLoadOrderLinks.size() list_entry_regions.append( (module.obj_offset, module.InLoadOrderLinks.obj_offset, list_entry_size * 3)) for module in task.get_mem_modules(): if module.DllBase not in inserted_bases: inserted_bases.append(module.DllBase) windows_insert_module(p_pid, p_pgd, module, update_symbols) if list_entry_size is None: list_entry_size = module.InLoadOrderLinks.size() list_entry_regions.append( (module.obj_offset, module.InLoadOrderLinks.obj_offset, list_entry_size * 3)) for module in task.get_load_modules(): if module.DllBase not in inserted_bases: inserted_bases.append(module.DllBase) windows_insert_module(p_pid, p_pgd, module, update_symbols) if list_entry_size is None: list_entry_size = module.InLoadOrderLinks.size() list_entry_regions.append( (module.obj_offset, module.InLoadOrderLinks.obj_offset, list_entry_size * 3)) # Remove all the modules that are not marked as present clean_non_present_modules(p_pid, p_pgd) return list_entry_regions return None
def get_vads(pgd): ''' Get list of VAD regions using volatility ''' import volatility.obj as obj import volatility.win32.tasks as tasks import volatility.plugins.vadinfo as vadinfo from utils import get_addr_space # Get volatility address space using the function in utils addr_space = get_addr_space(pgd) # Get list of Task objects using volatility (EPROCESS executive objects) eprocs = [ t for t in tasks.pslist(addr_space) if t.Pcb.DirectoryTableBase.v() == pgd ] # Traverse the list of selected EPROCESSes for task in eprocs: # Get heap base for every process HEAP heaps = task.Peb.ProcessHeaps.dereference() # Get base for every DLL modules = [mod.DllBase for mod in task.get_load_modules()] # Get Stack base for every THREAD stacks = [] for thread in task.ThreadListHead.list_of_type("_ETHREAD", "ThreadListEntry"): teb = obj.Object("_TEB", offset=thread.Tcb.Teb, vm=task.get_process_address_space()) if teb: stacks.append(teb.NtTib.StackBase) # Traverse VAD tree for vad in task.VadRoot.traverse(): if vad is not None: # Determine if the VAD is a HEAP, STACK, or MODULE vad_type = "" if vad.Start in heaps: # Heaps vad_type = "H" elif vad.Start in modules: # Module vad_type = "M" elif vad.Start in stacks: # Stacks vad_type = "S" else: vad_type = "-" # Get protection flags try: protection = vadinfo.PROTECT_FLAGS.get( vad.VadFlags.Protection.v(), "") except Exception: traceback.print_exc() # Get mapped file file_name = "" try: control_area = vad.ControlArea # even if the ControlArea is not NULL, it is only meaningful # for shared (non private) memory sections. if vad.VadFlags.PrivateMemory != 1 and control_area: if control_area: file_object = vad.FileObject if file_object: file_name = file_object.file_name_with_device() except AttributeError: pass # Return VAD regions yield VADRegion(vad.Start, vad.End, file_name, str(vad.Tag), vad_type, (vad.VadFlags.PrivateMemory == 1), protection)
def windows_read_memory_mapped(pgd, addr, size, pte, is_pae, bitness): # Step 1: Traverse the VAD tree for the process with PGD, # and get the VAD that overlaps addr (if any) # Step 2: Check if the VAD has a ControlArea and a FilePointer, # and get the file path. # Step 3: Get Segment (pointed to by ControlArea), and get the pointer # to the first PrototypePTE. # Step 4: Compute offset of address with respect to the beginning # of the VAD, and compute which PrototypePTE corresponds to the address # No need to consider if the PTE points to the Prototype PTE here. # Step 6: Compute the offset in file for such PrototypePTE by looking at the # subsections pointed by the ControlArea. # Step 7: Finally, open the file, read the contents, and return them. import volatility.obj as obj import volatility.win32.tasks as tasks import volatility.plugins.vadinfo as vadinfo from utils import get_addr_space addr_space = get_addr_space(pgd) eprocs = [ t for t in tasks.pslist(addr_space) if t.Pcb.DirectoryTableBase.v() == pgd ] if len(eprocs) != 1: return None task = eprocs[0] vad = None # File name and offset for vad in task.VadRoot.traverse(): if addr >= vad.Start and addr < vad.End: break if vad is None: return None filename = None if vad.ControlArea is not None and vad.FileObject is not None: filename = str(vad.ControlArea.FileObject.FileName) if vad.ControlArea.Segment is None: return None # Compute page offset with respect to Start of the VAD, # and the corresponding prototype Page Table Entry pointed # by the Segment offset_on_vad = addr - vad.Start page_offset_on_vad = (offset_on_vad - (offset_on_vad & 0xFFF)) # Consider 4 KiB pages ppte_index = page_offset_on_vad / 0x1000 if ppte_index >= vad.ControlArea.Segment.TotalNumberOfPtes.v(): return None if bitness == 32 and is_pae: ppte_addr = vad.ControlArea.Segment.PrototypePte.v() + (ppte_index * 8) else: ppte_addr = vad.ControlArea.Segment.PrototypePte.v() + ( ppte_index * addr_space.profile.vtypes["_MMPTE_PROTOTYPE"][0]) # Read Subsections pointed by ControlArea visited_subsections = {} if "Subsection" in vad.members: subsect = vad.Subsection # There is no Subsection pointer in VAD # structure, so we just read after the ControlArea else: subsect = obj.Object( "_SUBSECTION", offset=(vad.ControlArea.v() + addr_space.profile.vtypes["_CONTROL_AREA"][0]), vm=addr_space) file_offset_to_read = None while file_offset_to_read is None and subsect is not None or subsect.v( ) != 0 and subsect.v() not in visited_subsections: visited_subsections.append(subsect.v()) # Get the PPTE address where the Subsection starts, # and compute the virtual address that it corresponds # to. ppte_addr = subsect.SubsectionBase.v() if bitness == 32 and is_pae: ppte_index = (subsect.SubsectionBase.v() - vad.ControlArea.Segment.PrototypePte.v()) / 8 else: ppte_index = (subsect.SubsectionBase.v() - vad.ControlArea.Segment.PrototypePte.v() ) / addr_space.profile.vtypes["_MMPTE_PROTOTYPE"][0] subsection_base = vad.Start + (ppte_index * 0x1000) subsection_size = subsect.PtesInSubsection.v() * 0x1000 subsection_file_offset = subsect.StartingSector.v() * 512 subsection_file_size = vad.Subsection.NumberOfFullSectors.v() * 512 visited_subsections[subsect.v()] = (subsection_base, subsection_size, subsection_file_offset, subsection_file_size) if (addr >= subsection_base) and (addr < (subsection_base + subsection_size)): file_offset_to_read = (addr - subsection_base) + subsection_file_offset subsect = subsect.NextSubsection f = None for fs in api.get_filesystems(): try: f = api.open_guest_path(fs["index"], filename) break except: # The file cannot be open on such filesystem pass if not f: raise RuntimeError( "Could not read memory from pagefile: file not found") print("Reading file %s at offset %x - Size: %x" % (filename, file_offset_to_read, size)) f.seek(file_offset_to_read) data = f.read(size=size) f.close()
def update_vads(self): ''' Call volatility to obtain VADS. ''' if self.unpickled: return import volatility.obj as obj import volatility.win32.tasks as tasks import volatility.plugins.vadinfo as vadinfo from utils import get_addr_space addr_space = get_addr_space(self.get_pgd()) eprocs = [t for t in tasks.pslist( addr_space) if t.UniqueProcessId == self.pid] for task in eprocs: heaps = task.Peb.ProcessHeaps.dereference() modules = [mod.DllBase for mod in task.get_load_modules()] stacks = [] for thread in task.ThreadListHead.list_of_type("_ETHREAD", "ThreadListEntry"): teb = obj.Object("_TEB", offset=thread.Tcb.Teb, vm=task.get_process_address_space()) if teb: stacks.append(teb.NtTib.StackBase) for vad in task.VadRoot.traverse(): if vad is not None: vad_type = "" if vad.Start in heaps: # Heaps vad_type = "H" elif vad.Start in modules: # Module vad_type = "M" elif vad.Start in stacks: # Stacks vad_type = "S" else: vad_type = "-" try: protection = vadinfo.PROTECT_FLAGS.get( vad.VadFlags.Protection.v(), "") except Exception: traceback.print_exc() fileNameWithDevice = "" try: control_area = vad.ControlArea # even if the ControlArea is not NULL, it is only meaningful # for shared (non private) memory sections. if vad.VadFlags.PrivateMemory != 1 and control_area: if control_area: file_object = vad.FileObject if file_object: fileNameWithDevice = file_object.file_name_with_device( ) except AttributeError: pass try: new_vad = VADRegion(vad.Start, (vad.End - vad.Start), self, fileNameWithDevice, str( vad.Tag), vad_type, (vad.VadFlags.PrivateMemory == 1), protection) except Exception: traceback.print_exc() if new_vad not in self.vads: self.vads.append(new_vad)
def windows_read_paged_out_memory(pgd, addr, size): import api import api_internal import struct from utils import get_addr_space VALID_BIT = 0x1 PROTOTYPE_BIT = 0x1 << 10 TRANSITION_BIT = 0x1 << 11 PPTE_VALID_BIT = 0x1 PPTE_TRANSITION_BIT = 0x1 << 11 PPTE_DIRTY_BIT = 0x1 << 6 PPTE_P_BIT = 0x1 << 10 # Thit bit means it is a... #...memory mapped file, instead of "prototype" # Get PTE and 'mode' pte = api_internal.x86_get_pte(pgd, addr) is_pae = api_internal.x86_is_pae() bitness = api.get_os_bits() addr_space = get_addr_space(pgd) # if PTE is None, it could mean that the page directory has not been created. # if PTE is 0, it could mean the pte has not been yet created. # In these cases, we still check the VAD to see if it corresponds to a # memory mapped file, and if so, read that file at the correct offset if pte is None or pte == 0: return windows_read_memory_mapped(pgd, addr, size, pte, is_pae, bitness) # Make sure the page is invalid and we cannot read it: if (pte & VALID_BIT) == 1: return api.r_va(pgd, addr, size) # The PTE is INVALID. First, we check it doesn't point to a # prototype page table entry (PPTE). if (pte & PROTOTYPE_BIT) == 0: # The page is in the pagefile, or is demand zero, or memory mapped if (pte & TRANSITION_BIT) == 0: number_bits_offset = 0 number_bits_number = 0 page_file_offset = 0 page_file_number = 0 if (bitness == 32 and not is_pae) or bitness == 64: offset_mask = generate_mask( addr_space.profile.vtypes["_MMPTE_SOFTWARE"][1] ["PageFileHigh"][1][1]["start_bit"], addr_space.profile.vtypes["_MMPTE_SOFTWARE"][1] ["PageFileHigh"][1][1]["end_bit"]) number_mask = generate_mask( addr_space.profile.vtypes["_MMPTE_SOFTWARE"][1] ["PageFileLow"][1][1]["start_bit"], addr_space.profile.vtypes["_MMPTE_SOFTWARE"][1] ["PageFileLow"][1][1]["end_bit"]) number_bits_offset = addr_space.profile.vtypes["_MMPTE_SOFTWARE"][1]["PageFileHigh"][1][1]["end_bit"] - \ addr_space.profile.vtypes["_MMPTE_SOFTWARE"][1]["PageFileHigh"][1][1]["start_bit"] number_bits_number = addr_space.profile.vtypes["_MMPTE_SOFTWARE"][1]["PageFileLow"][1][1]["end_bit"] - \ addr_space.profile.vtypes["_MMPTE_SOFTWARE"][1]["PageFileLow"][1][1]["start_bit"] page_file_offset = ( pte & offset_mask ) >> addr_space.profile.vtypes["_MMPTE_SOFTWARE"][1][ "PageFileHigh"][1][1]["start_bit"] page_file_number = ( pte & number_mask) >> addr_space.profile.vtypes[ "_MMPTE_SOFTWARE"][1]["PageFileLow"][1][1]["start_bit"] elif bitness == 32 and is_pae: # See Intel manual, consider 24 bits of address for the 4KiB page offset # Page file offset should correspond to the same 24 bits offset_mask = generate_mask(12, 12 + 24) # Reuse the same as for 32/64 bits number_mask = generate_mask( addr_space.profile.vtypes["_MMPTE_SOFTWARE"][1] ["PageFileLow"][1][1]["start_bit"], addr_space.profile.vtypes["_MMPTE_SOFTWARE"][1] ["PageFileLow"][1][1]["end_bit"]) number_bits_offset = 24 number_bits_number = addr_space.profile.vtypes["_MMPTE_SOFTWARE"][1]["PageFileLow"][1][1]["end_bit"] - \ addr_space.profile.vtypes["_MMPTE_SOFTWARE"][1]["PageFileLow"][1][1]["start_bit"] page_file_offset = (pte & offset_mask) >> 12 page_file_number = ( pte & number_mask) >> addr_space.profile.vtypes[ "_MMPTE_SOFTWARE"][1]["PageFileLow"][1][1]["start_bit"] else: raise NotImplementedError() #Demand zero if page_file_offset == 0x0: return "\x00" * size #Check VAD (memory mapped file) (all 1's) elif page_file_offset == generate_mask(0, number_bits_offset): return windows_read_memory_mapped(pgd, addr, size, pte, is_pae, bitness) # Page file else: return windows_read_paged_file(pgd, addr, size, page_file_offset, page_file_number) # Transition page -> Can be read normally from memory, # so proceed with the read even if valid bit is 0. else: # Get the offset from the PTE, and compute ourselves the physical address page_offset = 0 if (bitness == 32 and not is_pae) or bitness == 64: offset_mask = generate_mask( addr_space.profile.vtypes["_MMPTE_HARDWARE"][1] ["PageFrameNumber"][1][1]["start_bit"], addr_space.profile.vtypes["_MMPTE_HARDWARE"][1] ["PageFrameNumber"][1][1]["end_bit"]) page_offset = (pte & offset_mask) elif bitness == 32 and is_pae: # See Intel manual, consider 24 bits of address for the 4KiB page offset # Page file offset should correspond to the same 24 bits offset_mask = generate_mask(12, 12 + 24) # Reuse the same as for 32/64 bits page_offset = (pte & offset_mask) else: raise NotImplementedError() # Read physical address, always 12 bits for a 4KiB page. # XXX: Here, we should should also consider 4Mb pages. return api.r_pa(page_offset | (addr & generate_mask(0, 12)), size) # The page points to a prototype PTE else: # We read the PPTE ppte_addr = 0 ppte_size = 0 if bitness == 32 and not is_pae: # In this case, the PPTE pointer is not a pointer, but an index, so it # needs some additional computation index_low_mask = generate_mask( addr_space.profile.vtypes["_MMPTE_PROTOTYPE"][1] ["ProtoAddressLow"][1][1]["start_bit"], addr_space.profile.vtypes["_MMPTE_PROTOTYPE"][1] ["ProtoAddressLow"][1][1]["end_bit"]) index_low = (pte & index_low_mask) >> addr_space.profile.vtypes[ "_MMPTE_PROTOTYPE"][1]["ProtoAddressLow"][1][1]["start_bit"] index_high_mask = generate_mask( addr_space.profile.vtypes["_MMPTE_PROTOTYPE"][1] ["ProtoAddressHigh"][1][1]["start_bit"], addr_space.profile.vtypes["_MMPTE_PROTOTYPE"][1] ["ProtoAddressHigh"][1][1]["end_bit"]) index_high = (pte & index_high_mask) >> addr_space.profile.vtypes[ "_MMPTE_PROTOTYPE"][1]["ProtoAddressHigh"][1][1]["start_bit"] number_bits_index_low = addr_space.profile.vtypes["_MMPTE_PROTOTYPE"][1]["ProtoAddressLow"][1][1]["end_bit"]- \ addr_space.profile.vtypes["_MMPTE_PROTOTYPE"][1]["ProtoAddressLow"][1][1]["start_bit"] ppte_size = addr_space.profile.vtypes["_MMPTE"][0] # Formula to compute the index index = ((index_high << number_bits_index_low) | index_low) << 2 # The index is an address relative to the base of a paged pool. # By debugging several systems, these bases where fixed (on 32 bit systems) # to 0x80000000 or 0xe1000000 (windows 7 and windows xp respectively). # This address points to one of the PrototypePTEs that are part of the Segment, # pointed out by the ControlArea of the corresponding VAD. Therefore, we should # be able to bruteforce the first 8 bits of the address to find the correct base. # First, get the Segment, and the first prototype PTE, as well as the number of # prototype PTEs for that Segment. res = windows_get_prototype_pte_address_range(pgd, addr) found = False if res is not None: start, end = res for i in range(0, 255): ppte_addr = (i << 24) | index if ppte_addr >= start and ppte_addr <= end: found = True break if not found: raise RuntimeError( "Could not read memory on second chance (using filesystem)" ) else: raise RuntimeError( "Could not read memory on second chance (using filesystem)" ) elif bitness == 32 and is_pae: # According to paper: "Windows Operating Systems Agnostic Memory Analysis" offset_mask = generate_mask(32, 64) ppte_addr = (pte & offset_mask) >> 32 ppte_size = 64 elif bitness == 64: offset_mask = generate_mask( addr_space.profile.vtypes["_MMPTE_PROTOTYPE"][1] ["ProtoAddress"][1][1]["start_bit"], addr_space.profile.vtypes["_MMPTE_PROTOTYPE"][1] ["ProtoAddress"][1][1]["end_bit"]) ppte_addr = (pte & offset_mask) >> addr_space.profile.vtypes[ "_MMPTE_PROTOTYPE"][1]["ProtoAddress"][1][1]["start_bit"] ppte_size = addr_space.profile.vtypes["_MMPTE"][0] else: raise NotImplementedError() # Now, read the PPTE given its address. The PPTE address is a virtual address!!!! (on a paged pool) if ppte_size == 4: ppte = struct.unpack("<I", api.r_va(pgd, ppte_addr, 4))[0] elif ppte_size == 8: ppte = struct.unpack("<K", api.r_va(pgd, ppte_addr, 8))[0] else: raise NotImplementedError() if (ppte & PPTE_VALID_BIT == 1) or (ppte & PPTE_TRANSITION_BIT == 1): # State: Active/Valid - Transision - Modified-no-write # The PPTE contains a valid entry, so we can # just use the info in it to translate the page. # Get the offset from the PTE, and compute ourselves the physical address page_offset = 0 if (bitness == 32 and not is_pae) or bitness == 64: offset_mask = generate_mask( addr_space.profile.vtypes["_MMPTE_HARDWARE"][1] ["PageFrameNumber"][1][1]["start_bit"], addr_space.profile.vtypes["_MMPTE_HARDWARE"][1] ["PageFrameNumber"][1][1]["end_bit"]) page_offset = (ppte & offset_mask) elif bitness == 32 and is_pae: # See Intel manual, consider 24 bits of address for the 4KiB page offset # Page file offset should correspond to the same 24 bits offset_mask = generate_mask(12, 12 + 24) # Reuse the same as for 32/64 bits page_offset = (ppte & offset_mask) else: raise NotImplementedError() # Read physical address, always 12 bits for a 4KiB page. # XXX: Here, we should should also consider 4Mb pages. return api.r_pa(page_offset | (addr & generate_mask(0, 12)), size) elif (ppte & (PPTE_VALID_BIT | PPTE_TRANSITION_BIT | PPTE_P_BIT)) == 0: # Demand zero or pagefile #Read page_file_offset and page_file_number page_file_offset = 0 page_file_number = 0 if (bitness == 32 and not is_pae) or bitness == 64: offset_mask = generate_mask( addr_space.profile.vtypes["_MMPTE_SOFTWARE"][1] ["PageFileHigh"][1][1]["start_bit"], addr_space.profile.vtypes["_MMPTE_SOFTWARE"][1] ["PageFileHigh"][1][1]["end_bit"]) number_mask = generate_mask( addr_space.profile.vtypes["_MMPTE_SOFTWARE"][1] ["PageFileLow"][1][1]["start_bit"], addr_space.profile.vtypes["_MMPTE_SOFTWARE"][1] ["PageFileLow"][1][1]["end_bit"]) page_file_offset = ( pte & offset_mask ) >> addr_space.profile.vtypes["_MMPTE_SOFTWARE"][1][ "PageFileHigh"][1][1]["start_bit"] page_file_number = ( pte & number_mask) >> addr_space.profile.vtypes[ "_MMPTE_SOFTWARE"][1]["PageFileLow"][1][1]["start_bit"] elif bitness == 32 and is_pae: # See Intel manual, consider 24 bits of address for the 4KiB page offset # Page file offset should correspond to the same 24 bits offset_mask = generate_mask(12, 12 + 24) # Reuse the same as for 32/64 bits number_mask = generate_mask( addr_space.profile.vtypes["_MMPTE_SOFTWARE"][1] ["PageFileLow"][1][1]["start_bit"], addr_space.profile.vtypes["_MMPTE_SOFTWARE"][1] ["PageFileLow"][1][1]["end_bit"]) page_file_offset = (pte & offset_mask) >> 12 page_file_number = ( pte & number_mask) >> addr_space.profile.vtypes[ "_MMPTE_SOFTWARE"][1]["PageFileLow"][1][1]["start_bit"] else: raise NotImplementedError() if page_file_offset == 0 and page_file_number == 0: #Demand zero return "\x00" * size else: # PageFile return windows_read_paged_file(pgd, addr, size, page_file_offset, page_file_number) elif (ppte & PPTE_P_BIT) == 1: # Memory mapped file return windows_read_memory_mapped(pgd, addr, size, ppte, is_pae, bitness)
def dump(params, pid=None, proc=None, update_vads=None, from_addr=None, callback_name=None, terminate_process=False): ''' Dump the process, modules, vads... ''' import volatility.constants as constants import volatility.exceptions as exceptions import volatility.obj as obj import volatility.win32.tasks as tasks from mw_monitor_classes import mwmon from utils import get_addr_space import api TARGET_LONG_SIZE = api.get_os_bits() / 8 cpu_index = params["cpu_index"] cpu = params["cpu"] mwmon.printer("Dumping process...") proc_hdl = None if terminate_process: if TARGET_LONG_SIZE == 4: # ret, proc_hdl, exit_status try: _, proc_hdl, _ = struct.unpack( "<III", api.r_va(api.get_running_process(cpu_index), cpu.ESP, 4 * 3)) except: proc_hdl = 0 mwmon.printer("Could not dereference process handle in dumper.py") elif TARGET_LONG_SIZE == 8: # We don't need the return address # ret = struct.unpack("<Q", # api.r_va(api.get_running_process(cpu_index), cpu.ESP, 8))[0] proc_hdl = cpu.RCX # We don't need the exit status # exit_status = cpu.RDX # It seems there are usually 2 calls, when a process terminates itself. # First, ZwTerminateProcess is called with 0 as proc_hdl, and afterwards # -1. if proc_hdl == 0: return if callback_name is not None: # First, remove callback mwmon.cm.rm_callback(callback_name) # Check if we have been called from the right from_addr if from_addr is not None: if TARGET_LONG_SIZE == 4: try: buff = api.r_va(api.get_running_process(cpu_index), cpu.ESP, 4) ret_addr = struct.unpack("<I", buff)[0] except: ret_addr = 0 mwmon.printer("Could not dereference return address on dumper.py") elif TARGET_LONG_SIZE == 8: try: buff = api.r_va(api.get_running_process(cpu_index), cpu.RSP, 8) ret_addr = struct.unpack("<Q", buff)[0] except: ret_addr = 0 mwmon.printer("Could not dereference return address on dumper.py") if from_addr != ret_addr: return # We have been called from the right point, now, dump. path = mwmon.dumper_path # Dump a file with the VAD info, etc, and a filename for each dumped file, # so that we can import feed IDA with it try: # Dump main executable. addr_space = get_addr_space() # If 1 handle is specified, get the pid for that handle instead # of the calling PID. if proc_hdl is not None: if (TARGET_LONG_SIZE == 4 and proc_hdl == 0xFFFFFFFF) or \ (TARGET_LONG_SIZE == 8 and proc_hdl == 0xFFFFFFFFFFFFFFFF): # If the handle is 0xFFFFFFFF, then the process is the caller. pass else: eprocs = [t for t in tasks.pslist( addr_space) if t.UniqueProcessId == pid] proc_obj = None # Search handle table for the new created process for task in eprocs: if task.UniqueProcessId == pid and task.ObjectTable.HandleTableList: for handle in task.ObjectTable.handles(): if handle.is_valid() and handle.HandleValue == proc_hdl and handle.get_object_type() == "Process": proc_obj = handle.dereference_as("_EPROCESS") break break if proc_obj is not None: # If we found the handle to which it referred, update the # corresponding pid pid = int(proc_obj.UniqueProcessId) else: return # Case when no PID is specified, just dump everything if pid is None: pids = [p.pid for p in mwmon.data.procs] # When one pid is specified, dump that PID else: pids = [pid] eprocs = [t for t in tasks.pslist( addr_space) if t.UniqueProcessId in pids] for task in eprocs: mwmon.printer("Dumping process %x" % (task.UniqueProcessId)) # Code adapted from procdump (volatility) task_space = task.get_process_address_space() if task_space is None: mwmon.printer("Error: Cannot acquire process AS") return elif task.Peb is None: # we must use m() here, because any other attempt to # reference task.Peb will try to instantiate the _PEB mwmon.printer( "Error: PEB at {0:#x} is unavailable (possibly due to paging)".format(task.m('Peb'))) return elif task_space.vtop(task.Peb.ImageBaseAddress) is None: mwmon.printer( "Error: ImageBaseAddress at {0:#x} is unavailable" + "(possibly due to paging)".format(task.Peb.ImageBaseAddress)) return else: mwmon.printer( "Dumping executable for %x" % (task.UniqueProcessId)) dump_file = os.path.join( path, "executable.%x.exe" % (task.UniqueProcessId)) of = open(dump_file, 'wb') pe_file = obj.Object( "_IMAGE_DOS_HEADER", offset=task.Peb.ImageBaseAddress, vm=task_space) try: for offset, code in pe_file.get_image(unsafe=True, memory=False, fix=True): of.seek(offset) of.write(code) except ValueError, ve: mwmon.printer("Error: {0}".format(ve)) return except exceptions.SanityCheckException, ve: mwmon.printer("Error: {0} Try -u/--unsafe".format(ve)) return