def block_begin(params): global cm from api import CallbackManager import api from ipython_shell import start_shell cpu_index = params["cpu_index"] cpu = params["cpu"] tb = params["tb"] pgd = api.get_running_process(cpu_index) start_shell() if api.get_os_bits() == 32: pyrebox_print("Adding optimized callback at 0x100218f\n") cm.add_callback(CallbackManager.BLOCK_BEGIN_CB, optimized_block_begin, name="block_begin_optimized", addr=0x100218f, pgd=pgd) elif api.get_os_bits() == 64: pyrebox_print("Adding optimized callback at 0xfffff96000139f20\n") cm.add_callback(CallbackManager.BLOCK_BEGIN_CB, optimized_block_begin, name="block_begin_optimized", addr=0xfffff96000139f20, pgd=pgd) api.stop_monitoring_process(pgd) pyrebox_print("Stopped monitoring process\n") cm.rm_callback("block_begin") pyrebox_print("Unregistered callback\n")
def block_begin(cpu_index, cpu, tb): global cm from api import CallbackManager import api from ipython_shell import start_shell pgd = api.get_running_process(cpu_index) start_shell() if api.get_os_bits() == 32: pyrebox_print("Adding optimized callback at 0x100218f\n") cm.add_callback(CallbackManager.BLOCK_BEGIN_CB, optimized_block_begin, name="block_begin_optimized", addr=0x100218f, pgd=pgd) elif api.get_os_bits() == 64: pyrebox_print("Adding optimized callback at 0xfffff96000139f20\n") cm.add_callback(CallbackManager.BLOCK_BEGIN_CB, optimized_block_begin, name="block_begin_optimized", addr=0xfffff96000139f20, pgd=pgd) api.stop_monitoring_process(pgd) pyrebox_print("Stopped monitoring process\n") cm.rm_callback("block_begin") pyrebox_print("Unregistered callback\n")
def __init__(self, proc_name): import api self.TARGET_LONG_SIZE = api.get_os_bits() / 8 self.__proc_num = Process.proc_counter Process.proc_counter += 1 self.__proc_name = proc_name self.__pgd = 0 self.__pid = 0 self.__modules = {} # Record of API calls (related to VADs, and others self.__vads = [] # Chunks of memory injected to other processes self.__injections = [] self.__file_operations = [] self.__section_maps = [] # Indicates that this instance is a result of unpicking a serialized # object self.__unpickled = False # Exited. Indicates that process has already exited. self.__exited = False # Whether or not the process is a Wow64 process self.__wow64 = False self.__symbols = [] self.__other_calls = [] self.__all_calls = []
def ntmapviewofsection_ret(params, cm, callback_name, mapped_sec, mapping_proc, base_p, size_p, offset_p, proc, update_vads, long_size): from core import SectionMap import api global interproc_data global interproc_config TARGET_LONG_SIZE = api.get_os_bits() / 8 cpu_index = params["cpu_index"] cpu = params["cpu"] pgd = api.get_running_process(cpu_index) # First, remove callback cm.rm_callback(callback_name) if base_p != 0: base = dereference_target_long(base_p, pgd, long_size) else: base = 0 if size_p != 0: size = dereference_target_long(size_p, pgd, long_size) else: size = 0 # Offset is always 8 bytes if offset_p != 0: try: offset = struct.unpack("Q", api.r_va(pgd, offset_p, 8))[0] except: offset = 0 pp_debug( "Could not dereference offset in NtMapViewOfSection return, in interproc_callbacks.py\n" ) else: offset = 0 mapping_proc.add_section_map( SectionMap(mapped_sec, pgd, base, size, offset)) if interproc_config.interproc_text_log and interproc_config.interproc_text_log_handle is not None: f = interproc_config.interproc_text_log_handle if TARGET_LONG_SIZE == 4: f.write( "[PID: %08x] NtMapViewOfSection: Base: %08x Size: %08x Offset: %08x / Section: %s\n" % (proc.get_pid(), base, size, offset, mapped_sec.get_backing_file())) elif TARGET_LONG_SIZE == 8: f.write( "[PID: %08x] NtMapViewOfSection: Base: %16x Size: %16x Offset: %08x / Section: %s\n" % (proc.get_pid(), base, size, offset, mapped_sec.get_backing_file())) if update_vads: proc.update_vads()
def __handle_host_request_exec_args_linux(self, cpu_index, cpu): """ Handle the host_request_exec_args interface call. Argument in EAX: the buffer to write to Argument in EBX: the max size of the buffer to write to Returns number of bytes written in EAX, or -1 if the call failed. """ if isinstance(cpu, X86CPU): buf = cpu.EAX size = cpu.EBX elif isinstance(cpu, X64CPU): buf = cpu.RAX size = cpu.RBX TARGET_LONG_SIZE = api.get_os_bits() / 8 args = self.__file_to_execute["args"] argv_size = TARGET_LONG_SIZE * (len(args) + 1) + sum( len(x) + 1 for x in args) if argv_size > self.__agent_buffer_size: raise ValueError( "The size of the args should not exceed %d bytes" % self.__agent_buffer_size) # self.__printer("GuestAgentPlugin: host_request_exec_args(0x%08x, %d) # called" % (buf, size)) pgd = api.get_running_process(cpu_index) try: # Security check: the buffer should be located on the allowed # boundaries if self.__check_buffer_validity(buf, size): self.__write_strings_array(pgd, buf, args) if isinstance(cpu, X86CPU): api.w_r(cpu_index, "EAX", argv_size) elif isinstance(cpu, X64CPU): api.w_r(cpu_index, "RAX", argv_size) else: self.__printer( "HostFilesPlugin: Declared buffer or buffer size are not" + "within the allowed boundaries %x (%x)" % (buf, size)) if isinstance(cpu, X86CPU): api.w_r(cpu_index, "EAX", -1) elif isinstance(cpu, X64CPU): api.w_r(cpu_index, "RAX", -1) except Exception as ex: self.__printer( "HostFilesPlugin: Exception %s while trying to write file args to guest" % (str(ex))) if isinstance(cpu, X86CPU): api.w_r(cpu_index, "EAX", -1) elif isinstance(cpu, X64CPU): api.w_r(cpu_index, "RAX", -1)
def __handle_host_request_exec_args_linux(self, cpu_index, cpu): """ Handle the host_request_exec_args interface call. Argument in EAX: the buffer to write to Argument in EBX: the max size of the buffer to write to Returns number of bytes written in EAX, or -1 if the call failed. """ if isinstance(cpu, X86CPU): buf = cpu.EAX size = cpu.EBX elif isinstance(cpu, X64CPU): buf = cpu.RAX size = cpu.RBX TARGET_LONG_SIZE = api.get_os_bits() / 8 args = self.__file_to_execute["args"] argv_size = TARGET_LONG_SIZE * (len(args) + 1) + sum(len(x) + 1 for x in args) if argv_size > self.__agent_buffer_size: raise ValueError("The size of the args should not exceed %d bytes" % self.__agent_buffer_size) # self.__printer("GuestAgentPlugin: host_request_exec_args(0x%08x, %d) # called" % (buf, size)) pgd = api.get_running_process(cpu_index) try: # Security check: the buffer should be located on the allowed # boundaries if self.__check_buffer_validity(buf, size): self.__write_strings_array( pgd, buf, args) if isinstance(cpu, X86CPU): api.w_r( cpu_index, "EAX", argv_size) elif isinstance(cpu, X64CPU): api.w_r( cpu_index, "RAX", argv_size) else: self.__printer("HostFilesPlugin: Declared buffer or buffer size are not" + "within the allowed boundaries %x (%x)" % (buf, size)) if isinstance(cpu, X86CPU): api.w_r(cpu_index, "EAX", -1) elif isinstance(cpu, X64CPU): api.w_r(cpu_index, "RAX", -1) except Exception as ex: self.__printer( "HostFilesPlugin: Exception %s while trying to write file args to guest" % (str(ex))) if isinstance(cpu, X86CPU): api.w_r(cpu_index, "EAX", -1) elif isinstance(cpu, X64CPU): api.w_r(cpu_index, "RAX", -1)
def read_parameters(cpu, num_params): ''' Reads parameters from the registers/stack ''' import api TARGET_LONG_SIZE = api.get_os_bits() / 8 if TARGET_LONG_SIZE == 4: # All the parameters are on the stack # We need to read num_params values + the return address try: buff = api.r_va(cpu.CR3, cpu.ESP, (num_params + 1) * 4) except: buff = "\x00" * len((num_params + 1) * 4) mwmon.printer( "Could not read properly the parameters in interproc.py") params = struct.unpack("<" + "I" * (1 + num_params), buff) return params elif TARGET_LONG_SIZE == 8: params_regs = [] params_stack = () # Add the return address as parameter 0 try: buff = api.r_va(cpu.CR3, cpu.RSP, 8) except: buff = "\x00" * 8 mwmon.printer( "Could not read properly the parameters in interproc.py") params_regs.append(struct.unpack("<Q", buff)[0]) if num_params <= 1: params_regs.append(cpu.RCX) if num_params <= 2: params_regs.append(cpu.RDX) if num_params <= 3: params_regs.append(cpu.R8) if num_params <= 4: params_regs.append(cpu.R9) if num_params > 4: # We need to read num_params + the return address + 0x20 # 0x20 is for the 4 slots (of 8 bytes) allocated to store # register parameters (allocated by caller, used by callee) try: buff = api.r_va(cpu.CR3, cpu.RSP, (num_params + 5) * 8) except: buff = "\x00" * ((num_params + 5) * 8) mwmon.printer( "Could not read properly the parameters in interproc.py") params_stack = struct.unpack("<" + "Q" * (5 + num_params), buff) params_stack = params_stack[5:] return (tuple(params_regs) + params_stack) else: raise Exception( "[interproc::read_return_parameter(cpu)] : Non-supported TARGET_LONG_SIZE: %d" % TARGET_LONG_SIZE)
def ntmapviewofsection_ret(params, pid, callback_name, mapped_sec, mapping_proc, base_p, size_p, offset_p, proc, update_vads): from mw_monitor_classes import mwmon from mw_monitor_classes import SectionMap 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) # First, remove callback mwmon.cm.rm_callback(callback_name) if base_p != 0: base = dereference_target_long(base_p, pgd) else: base = 0 if size_p != 0: size = dereference_target_long(size_p, pgd) else: size = 0 # Offset is always 8 bytes if offset_p != 0: try: offset = struct.unpack("Q", api.r_va(pgd, offset_p, 8))[0] except: offset = 0 mwmon.printer("Could not dereference offset in NtMapViewOfSection return, in interproc.py") else: offset = 0 mapping_proc.section_maps.append( SectionMap(mapped_sec, base, size, offset)) 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] NtMapViewOfSection: Base: %08x Size: %08x Offset: %08x / Section: %s\n" % (pid, base, size, offset, mapped_sec.backing_file)) elif TARGET_LONG_SIZE == 8: f.write("[PID: %x] NtMapViewOfSection: Base: %16x Size: %16x Offset: %08x / Section: %s\n" % (pid, base, size, offset, mapped_sec.backing_file)) if update_vads: proc.update_vads()
def dereference_target_long(addr, pgd): import api TARGET_LONG_SIZE = api.get_os_bits() / 8 typ = "<I" if TARGET_LONG_SIZE == 4 else "<Q" try: buff = api.r_va(pgd, addr, TARGET_LONG_SIZE) except: buff = "\x00" * TARGET_LONG_SIZE mwmon.printer("Could not dereference TARGET_LONG in interproc.py") return struct.unpack(typ, buff)[0]
def log_calls(): import api from interproc import interproc_data TARGET_LONG_SIZE = api.get_os_bits() / 8 f_out = open(APITRACER_TEXT_LOG_PATH, "w") try: for proc in interproc_data.get_processes(): f_out.write("Process (PID: %x) %s\n" % (proc.get_pid(), proc.get_proc_name())) for vad in proc.get_vads(): if len(vad.get_calls()) > 0: if TARGET_LONG_SIZE == 4: f_out.write( "\n\nVAD [%08x - %08x] - %s\n\n" % (vad.get_start(), vad.get_size(), (vad.get_mapped_file() if vad.get_mapped_file() is not None else "Not file mapped"))) elif TARGET_LONG_SIZE == 8: f_out.write( "\n\nVAD [%016x - %016x] - %s\n\n" % (vad.get_start(), vad.get_size(), (vad.get_mapped_file() if vad.get_mapped_file() is not None else "Not file mapped"))) for data in vad.get_calls(): f_out.write("%s" % data[2].__str__()) if len(proc.get_other_calls()) > 0: f_out.write("\n\n OTHER CALLS...\n\n") for call in proc.get_other_calls(): f_out.write("%s" % data[2].__str__()) if f_out is not None: f_out.close() except Exception as e: pyrebox_print(str(e)) pyrebox_print(traceback.print_exc()) # Output ordered calls f_out = open(APITRACER_TEXT_LOG_PATH + ".ordered", "w") try: for proc in interproc_data.get_processes(): f_out.write("Process (PID: %x) %s\n" % (proc.get_pid(), proc.get_proc_name())) for data in proc.get_all_calls(): f_out.write("%s" % data[2].__str__()) if f_out is not None: f_out.close() except Exception as e: pyrebox_print(str(e)) pyrebox_print(traceback.print_exc())
def read_parameters(cpu, num_params): ''' Reads parameters from the registers/stack ''' import api TARGET_LONG_SIZE = api.get_os_bits() / 8 if TARGET_LONG_SIZE == 4: # All the parameters are on the stack # We need to read num_params values + the return address try: buff = api.r_va(cpu.CR3, cpu.ESP, (num_params + 1) * 4) except: buff = "\x00" * len((num_params + 1) * 4) mwmon.printer("Could not read properly the parameters in interproc.py") params = struct.unpack("<" + "I" * (1 + num_params), buff) return params elif TARGET_LONG_SIZE == 8: params_regs = [] params_stack = () # Add the return address as parameter 0 try: buff = api.r_va(cpu.CR3, cpu.RSP, 8) except: buff = "\x00" * 8 mwmon.printer("Could not read properly the parameters in interproc.py") params_regs.append(struct.unpack("<Q", buff)[0]) if num_params <= 1: params_regs.append(cpu.RCX) if num_params <= 2: params_regs.append(cpu.RDX) if num_params <= 3: params_regs.append(cpu.R8) if num_params <= 4: params_regs.append(cpu.R9) if num_params > 4: # We need to read num_params + the return address + 0x20 # 0x20 is for the 4 slots (of 8 bytes) allocated to store # register parameters (allocated by caller, used by callee) try: buff = api.r_va(cpu.CR3, cpu.RSP, (num_params + 5) * 8) except: buff = "\x00" * ((num_params + 5) * 8) mwmon.printer("Could not read properly the parameters in interproc.py") params_stack = struct.unpack( "<" + "Q" * (5 + num_params), buff) params_stack = params_stack[5:] return (tuple(params_regs) + params_stack) else: raise Exception( "[interproc::read_return_parameter(cpu)] : Non-supported TARGET_LONG_SIZE: %d" % TARGET_LONG_SIZE)
def ntmapviewofsection_ret(params, pid, callback_name, mapped_sec, mapping_proc, base_p, size_p, offset_p, proc, update_vads): from mw_monitor_classes import mwmon from mw_monitor_classes import SectionMap 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) # First, remove callback mwmon.cm.rm_callback(callback_name) if base_p != 0: base = dereference_target_long(base_p, pgd) else: base = 0 if size_p != 0: size = dereference_target_long(size_p, pgd) else: size = 0 # Offset is always 8 bytes if offset_p != 0: try: offset = struct.unpack("Q", api.r_va(pgd, offset_p, 8))[0] except: offset = 0 mwmon.printer( "Could not dereference offset in NtMapViewOfSection return, in interproc.py" ) else: offset = 0 mapping_proc.section_maps.append(SectionMap(mapped_sec, base, size, offset)) 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] NtMapViewOfSection: Base: %08x Size: %08x Offset: %08x / Section: %s\n" % (pid, base, size, offset, mapped_sec.backing_file)) elif TARGET_LONG_SIZE == 8: f.write( "[PID: %x] NtMapViewOfSection: Base: %16x Size: %16x Offset: %08x / Section: %s\n" % (pid, base, size, offset, mapped_sec.backing_file)) if update_vads: proc.update_vads()
def ntallocatevirtualmemory_ret(params, cm, callback_name, mapping_proc=None, base_addr_p=None, zerobits=None, size_p=None, aloc_type=None, access=None, proc=None, update_vads=None, long_size=None): import api global interproc_data global interproc_config TARGET_LONG_SIZE = api.get_os_bits() / 8 cpu_index = params["cpu_index"] cpu = params["cpu"] pgd = api.get_running_process(cpu_index) # First, remove callback cm.rm_callback(callback_name) # Now, dereference all the output pointers # base and size_p depend on 32-bit vs. 64 bit. This should be turned into # 8 bytes for 64 bit guest. if base_addr_p != 0: base = dereference_target_long(base_addr_p, pgd, long_size) else: base = 0 if size_p != 0: size = dereference_target_long(size_p, pgd, long_size) else: size = 0 if interproc_config.interproc_text_log and interproc_config.interproc_text_log_handle is not None: f = interproc_config.interproc_text_log_handle if TARGET_LONG_SIZE == 4: f.write( "[PID: %08x] NtAllocateVirtualMemory: Base: %08x Size: %08x Protect: %08x\n" % (proc.get_pid(), base, size, access)) elif TARGET_LONG_SIZE == 8: f.write( "[PID: %08x] NtAllocateVirtualMemory: Base: %016x Size: %016x Protect: %016x\n" % (proc.get_pid(), base, size, access)) if update_vads: proc.update_vads()
def read_return_parameter(cpu): ''' Returns the return parameter (EAX/RAX) ''' import api TARGET_LONG_SIZE = api.get_os_bits() / 8 if TARGET_LONG_SIZE == 4: return cpu.EAX elif TARGET_LONG_SIZE == 8: return cpu.RAX else: raise Exception( "[interproc::read_return_parameter(cpu)] : Non-supported TARGET_LONG_SIZE: %d" % TARGET_LONG_SIZE)
def __write_strings_array(self, pgd, vaddr, array): """ Write a char* arg[] array. """ TARGET_LONG_SIZE = api.get_os_bits() / 8 array_ptr = vaddr strings_ptr = vaddr + TARGET_LONG_SIZE * (len(array) + 1) for i, s in enumerate(array): api.w_va(pgd, array_ptr, struct.pack( "<" + ("L" if TARGET_LONG_SIZE == 4 else "Q"), strings_ptr), TARGET_LONG_SIZE) api.w_va(pgd, strings_ptr, s + "\x00", len(s) + 1) array_ptr += TARGET_LONG_SIZE strings_ptr += len(s) + 1 api.w_va(pgd, array_ptr, "\x00" * TARGET_LONG_SIZE, TARGET_LONG_SIZE)
def block_executed(cpu_index, cpu, tb, proc=None): import api TARGET_LONG_SIZE = api.get_os_bits() / 8 # Get the overlapping VAD, if we don't have it, update VADs if TARGET_LONG_SIZE == 4: page = cpu.EIP & 0xFFFFF000 elif TARGET_LONG_SIZE == 8: page = cpu.RIP & 0xFFFFFFFFFFFFF000 vad = proc.get_overlapping_vad(page) if vad is None: proc.update_vads() return
def ntallocatevirtualmemory_ret(params, pid, callback_name, mapping_proc=None, base_addr_p=None, zerobits=None, size_p=None, aloc_type=None, access=None, proc=None, update_vads=None): from mw_monitor_classes import mwmon 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) # First, remove callback mwmon.cm.rm_callback(callback_name) # Now, dereference all the output pointers # base and size_p depend on 32-bit vs. 64 bit. This should be turned into # 8 bytes for 64 bit guest. if base_addr_p != 0: base = dereference_target_long(base_addr_p, pgd) else: base = 0 if size_p != 0: size = dereference_target_long(size_p, pgd) else: size = 0 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] NtAllocateVirtualMemory: Base: %08x Size: %08x Protect: %08x\n" % (pid, base, size, access)) elif TARGET_LONG_SIZE == 8: f.write("[PID: %x] NtAllocateVirtualMemory: Base: %016x Size: %016x Protect: %016x\n" % (pid, base, size, access)) if update_vads: proc.update_vads()
def log_calls(): from mw_monitor_classes import mwmon import api TARGET_LONG_SIZE = api.get_os_bits() / 8 f_out = open(mwmon.api_tracer_text_log_name, "w") try: for proc in mwmon.data.procs: if mwmon.api_tracer_procs is None or proc.proc_name in mwmon.api_tracer_procs: f_out.write("Process (PID: %x) %s\n" % (proc.pid, proc.proc_name)) for vad in proc.vads: if len(vad.get_calls()) > 0: if TARGET_LONG_SIZE == 4: f_out.write("\n\nVAD [%08x - %08x]\n\n" % (vad.start, vad.size)) elif TARGET_LONG_SIZE == 8: f_out.write("\n\nVAD [%016x - %016x]\n\n" % (vad.start, vad.size)) for data in vad.get_calls(): f_out.write("%s" % data[2].__str__()) if len(proc.other_calls) > 0: f_out.write("\n\n OTHER CALLS...\n\n") for call in proc.other_calls: f_out.write("%s" % data[2].__str__()) if f_out is not None: f_out.close() except Exception as e: mwmon.printer(str(e)) mwmon.printer(traceback.print_exc()) # Output ordered calls f_out = open(mwmon.api_tracer_text_log_name + ".ordered", "w") try: for proc in mwmon.data.procs: if mwmon.api_tracer_procs is None or proc.proc_name in mwmon.api_tracer_procs: f_out.write("Process (PID: %x) %s\n" % (proc.pid, proc.proc_name)) for data in proc.all_calls: f_out.write("%s" % data[2].__str__()) if f_out is not None: f_out.close() except Exception as e: mwmon.printer(str(e)) mwmon.printer(traceback.print_exc())
def log_calls(): from mw_monitor_classes import mwmon import api TARGET_LONG_SIZE = api.get_os_bits() / 8 f_out = open(mwmon.api_tracer_text_log_name, "w") try: for proc in mwmon.data.procs: if mwmon.api_tracer_procs is None or proc.proc_name in mwmon.api_tracer_procs: f_out.write("Process (PID: %x) %s\n" % (proc.pid, proc.proc_name)) for vad in proc.vads: if len(vad.get_calls()) > 0: if TARGET_LONG_SIZE == 4: f_out.write( "\n\nVAD [%08x - %08x]\n\n" % (vad.start, vad.size)) elif TARGET_LONG_SIZE == 8: f_out.write( "\n\nVAD [%016x - %016x]\n\n" % (vad.start, vad.size)) for data in vad.get_calls(): f_out.write("%s" % data[2].__str__()) if len(proc.other_calls) > 0: f_out.write("\n\n OTHER CALLS...\n\n") for call in proc.other_calls: f_out.write("%s" % data[2].__str__()) if f_out is not None: f_out.close() except Exception as e: mwmon.printer(str(e)) mwmon.printer(traceback.print_exc()) # Output ordered calls f_out = open(mwmon.api_tracer_text_log_name + ".ordered", "w") try: for proc in mwmon.data.procs: if mwmon.api_tracer_procs is None or proc.proc_name in mwmon.api_tracer_procs: f_out.write("Process (PID: %x) %s\n" % (proc.pid, proc.proc_name)) for data in proc.all_calls: f_out.write("%s" % data[2].__str__()) if f_out is not None: f_out.close() except Exception as e: mwmon.printer(str(e)) mwmon.printer(traceback.print_exc())
def ntdll_breakpoint_func(proc, cpu_index, cpu): ''' Breakpoint for the first instruction executed in the main module ''' global ntdll_breakpoint from mw_monitor_classes import mwmon import api ntdll_breakpoint[proc.get_pgd()].disable() TARGET_LONG_SIZE = api.get_os_bits() / 8 if TARGET_LONG_SIZE == 4: mwmon.printer("Executed first instruction for pgd %x at %x" % (cpu.CR3, cpu.EIP)) elif TARGET_LONG_SIZE == 8: mwmon.printer("Executed first instruction for pgd %x at %x" % (cpu.CR3, cpu.RIP)) proc.update_symbols()
def opcodes_ret(addr_from, addr_to, data, callback_name, argument_parser, mod, fun, proc, cpu_index, cpu): from mw_monitor_classes import mwmon import api TARGET_LONG_SIZE = api.get_os_bits() / 8 try: mwmon.cm.rm_callback(callback_name) if TARGET_LONG_SIZE == 4: argument_parser.update_return(cpu.EAX) elif TARGET_LONG_SIZE == 8: argument_parser.update_return(cpu.RAX) data.out_args = [arg for arg in argument_parser.get_out_args()] data.ret = argument_parser.get_ret() proc.add_call(addr_from, addr_to, data) except Exception as e: mwmon.printer("Exception: %s" % str(e)) finally: return
def ntdll_breakpoint_func(proc, params): ''' Breakpoint for the first instruction executed in the main module ''' global ntdll_breakpoint from mw_monitor_classes import mwmon import api cpu_index = params["cpu_index"] cpu = params["cpu"] ntdll_breakpoint[proc.get_pgd()].disable() TARGET_LONG_SIZE = api.get_os_bits() / 8 if TARGET_LONG_SIZE == 4: mwmon.printer("Executed first instruction for pgd %x at %x" % (cpu.CR3, cpu.EIP)) elif TARGET_LONG_SIZE == 8: mwmon.printer("Executed first instruction for pgd %x at %x" % (cpu.CR3, cpu.RIP)) proc.update_symbols()
def block_executed(params, proc=None): import api TARGET_LONG_SIZE = api.get_os_bits() / 8 cpu_index = params["cpu_index"] cpu = params["cpu"] tb = params["tb"] # Get the overlapping VAD, if we don't have it, update VADs if TARGET_LONG_SIZE == 4: page = cpu.EIP & 0xFFFFF000 elif TARGET_LONG_SIZE == 8: page = cpu.RIP & 0xFFFFFFFFFFFFF000 vad = proc.get_overlapping_vad(page) if vad is None: proc.update_vads() return
def __str__(self): import api TARGET_LONG_SIZE = api.get_os_bits() / 8 try: outstr = "" if TARGET_LONG_SIZE == 4: outstr += ( "\n\n\n[0x%08x] --> [%s:%s] --> [0x%08x]\n" % (self.__pc, self.__mod, self.__fun, self.__ret_addr)) elif TARGET_LONG_SIZE == 8: outstr += ( "\n\n\n[0x%016x] --> [%s:%s] --> [0x%016x]\n" % (self.__pc, self.__mod, self.__fun, self.__ret_addr)) args = sorted(self.__in_args + self.__out_args) for arg in args: if arg.is_output_arg(): try: outstr += ("[OUT] %s: %s\n" % (arg.get_arg_name(), arg.__str__())) except Exception as e: outstr += ("[OUT] %s: Unable to process: %s\n" % (arg.get_arg_name(), str(e))) else: try: outstr += ("[IN ] %s: %s\n" % (arg.get_arg_name(), arg.__str__())) except Exception as e: outstr += ("[IN] %s: Unable to process : %s\n" % (arg.get_arg_name(), str(e))) if self.__ret is not None and self.__ret is not "": try: outstr += ( "[RET] %s: %s\n" % (self.__ret.get_arg_name(), self.__ret.__str__())) except Exception as e: outstr += ("[RET] %s: Unable to process: %s\n" % (self.__ret.get_arg_name(), str(e))) return outstr except Exception as e: pyrebox_print(traceback.print_exc()) pyrebox_print(str(e))
def opcodes_ret(addr_from, addr_to, data, callback_name, argument_parser, mod, fun, proc, params): from mw_monitor_classes import mwmon import api TARGET_LONG_SIZE = api.get_os_bits() / 8 cpu_index = params["cpu_index"] cpu = params["cpu"] try: mwmon.cm.rm_callback(callback_name) if TARGET_LONG_SIZE == 4: argument_parser.update_return(cpu.EAX) elif TARGET_LONG_SIZE == 8: argument_parser.update_return(cpu.RAX) data.out_args = [arg for arg in argument_parser.get_out_args()] data.ret = argument_parser.get_ret() proc.add_call(addr_from, addr_to, data) except Exception as e: mwmon.printer("Exception: %s" % str(e)) finally: return
def opcodes_ret(addr_from, addr_to, data, callback_name, argument_parser, mod, fun, proc, params): import api global cm TARGET_LONG_SIZE = api.get_os_bits() / 8 cpu_index = params["cpu_index"] cpu = params["cpu"] try: cm.rm_callback(callback_name) if TARGET_LONG_SIZE == 4: argument_parser.update_return(cpu.EAX) elif TARGET_LONG_SIZE == 8: argument_parser.update_return(cpu.RAX) data.set_out_args([arg for arg in argument_parser.get_out_args()]) data.set_ret(argument_parser.get_ret()) except Exception as e: pyrebox_print("Exception: %s" % str(e)) finally: return
def __str__(self): from mw_monitor_classes import mwmon import api TARGET_LONG_SIZE = api.get_os_bits() / 8 try: outstr = "" if TARGET_LONG_SIZE == 4: outstr += ("\n\n\n[0x%08x] --> [%s:%s] --> [0x%08x]\n" % (self.pc, self.mod, self.fun, self.ret_addr)) elif TARGET_LONG_SIZE == 8: outstr += ("\n\n\n[0x%016x] --> [%s:%s] --> [0x%016x]\n" % (self.pc, self.mod, self.fun, self.ret_addr)) args = sorted(self.in_args + self.out_args) for arg in args: if arg.is_output_arg(): try: outstr += ("[OUT] %s: %s\n" % (arg.get_arg_name(), arg.__str__())) except Exception as e: outstr += ( "[OUT] %s: Unable to process: %s\n" % (arg.get_arg_name(), str(e))) else: try: outstr += ("[IN ] %s: %s\n" % (arg.get_arg_name(), arg.__str__())) except Exception as e: outstr += ( "[IN] %s: Unable to process : %s\n" % (arg.get_arg_name(), str(e))) if self.ret is not None and self.ret is not "": try: outstr += ("[RET] %s: %s\n" % (self.ret.get_arg_name(), self.ret.__str__())) except Exception as e: outstr += ("[RET] %s: Unable to process: %s\n" % (self.ret.get_arg_name(), str(e))) return outstr except Exception as e: mwmon.printer(traceback.print_exc()) mwmon.printer(str(e))
def populate_args(self): import api TARGET_LONG_SIZE = api.get_os_bits() / 8 if self.func_id is None: return [] self.c.execute( "select Id,Name,TypeId,TypeClass,IsOutput from FunctionsArgs where FuncId = %d order by Id ASC" % (self.func_id)) params = self.c.fetchall() # Here, we need to use properly the addresses depending on the bitness # and the calling convention if TARGET_LONG_SIZE == 4: # Skip return address addr = self.addr + 4 reg_params = [] elif TARGET_LONG_SIZE == 8: # 4 slots for saving arguments + return address addr = self.addr + 8 * 5 reg_params = [self.cpu.RCX, self.cpu.RDX, self.cpu.R8, self.cpu.R9] arg_num = 1 for param in params: # Unfold the argument is_out = False if param[4] == 0 else True if TARGET_LONG_SIZE == 8 and arg_num <= 4: arg = self.generate_arg(param[1], param[2], param[ 3], is_out, val=reg_params[arg_num - 1], arg_num=arg_num) else: arg = self.generate_arg( param[1], param[2], param[3], is_out, addr=addr, arg_num=arg_num) addr += len(arg) arg_num += 1 # Point to next parameter in stack self.args.append(arg)
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 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 log_coverage(): from mw_monitor_classes import mwmon import api import ntpath # Address and size will have as many bytes as an address # in the target architecture TARGET_LONG_SIZE = api.get_os_bits() / 8 for proc in mwmon.data.procs: if mwmon.coverage_procs is None or proc.proc_name in mwmon.coverage_procs: if os.path.isfile(mwmon.coverage_log_name + (".%x" % proc.pid)): try: current_vad = None f = open( mwmon.coverage_text_name + (".%x" % proc.pid), "w") f_in = open( mwmon.coverage_log_name + (".%x" % proc.pid), "rb") data = f_in.read(TARGET_LONG_SIZE + TARGET_LONG_SIZE) last_pc = 0 while data is not None and len(data) == 8: if TARGET_LONG_SIZE == 4: pc, size = struct.unpack("<II", data) elif TARGET_LONG_SIZE == 8: pc, size = struct.unpack("<QQ", data) else: raise Exception( "[log_coverage()] Unsupported TARGET_LONG_SIZE: %d" % TARGET_LONG_SIZE) # Locate nearest lower symbol sym = proc.locate_nearest_symbol(pc) sym_text = "" if sym is not None: # mod = sym.get_mod() fun = sym.get_fun() real_api_addr = sym.get_addr() if real_api_addr == pc: sym_text = " - %s" % fun else: sym_text = " - %s(+%x)" % ( fun, (pc - real_api_addr)) if current_vad is None: current_vad = proc.get_overlapping_vad(pc) if current_vad is not None: if TARGET_LONG_SIZE == 4: f.write("VAD: %08x(%08x) %08x --> %08x [%s%s]\n" % ( current_vad.start, current_vad.size, last_pc, pc, ntpath.basename(current_vad.mapped_file), sym_text)) elif TARGET_LONG_SIZE == 8: f.write("VAD: %16x(%16x) %16x --> %16x [%s%s]\n" % ( current_vad.start, current_vad.size, last_pc, pc, ntpath.basename(current_vad.mapped_file), sym_text)) else: raise Exception( "[log_coverage()] Unsupported TARGET_LONG_SIZE: %d" % TARGET_LONG_SIZE) else: new_vad = proc.get_overlapping_vad(pc) if new_vad != current_vad: current_vad = new_vad if TARGET_LONG_SIZE == 4: f.write("VAD: %08x(%08x) %08x --> %08x [%s%s]\n" % ( current_vad.start, current_vad.size, last_pc, pc, ntpath.basename(current_vad.mapped_file), sym_text)) elif TARGET_LONG_SIZE == 8: f.write("VAD: %16x(%16x) %16x --> %16x [%s%s]\n" % ( current_vad.start, current_vad.size, last_pc, pc, ntpath.basename(current_vad.mapped_file), sym_text)) else: raise Exception( "[log_coverage()] Unsupported TARGET_LONG_SIZE: %d" % TARGET_LONG_SIZE) data = f_in.read(TARGET_LONG_SIZE + TARGET_LONG_SIZE) # last_pc, last_size = pc, size last_pc = pc f_in.close() f.close() except Exception: traceback.print_exc()
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 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 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 __handle_host_request_exec_env_linux(self, cpu_index, cpu): """ Handle the host_request_exec_env interface call. Argument in EAX: the buffer to write to Argument in EBX: the max size of the buffer to write to Returns number of bytes written in EAX, or -1 if the call failed. """ if isinstance(cpu, X86CPU): buf = cpu.EAX size = cpu.EBX elif isinstance(cpu, X64CPU): buf = cpu.RAX size = cpu.RBX TARGET_LONG_SIZE = api.get_os_bits() / 8 env = self.__file_to_execute["env"] pgd = api.get_running_process(cpu_index) # self.__printer("GuestAgentPlugin: host_request_exec_env(0x%08x, %d) # called" % (buf, size)) if len(env) > 0: env = ["{:s}={:s}".format(k, v) for k, v in env.items()] env_size = sum(len(x) + 1 for x in env) + TARGET_LONG_SIZE * (len(env) + 1) try: # Security check: the buffer should be located on the allowed # boundaries if self.__check_buffer_validity(buf, size): self.__write_strings_array( pgd, buf, env) if isinstance(cpu, X86CPU): api.w_r( cpu_index, "EAX", env_size) elif isinstance(cpu, X64CPU): api.w_r( cpu_index, "RAX", env_size) else: self.__printer("HostFilesPlugin: Declared buffer or buffer size are not" + "within the allowed boundaries %x (%x)" % (buf, size)) if isinstance(cpu, X86CPU): api.w_r(cpu_index, "EAX", -1) elif isinstance(cpu, X64CPU): api.w_r(cpu_index, "RAX", -1) except Exception as ex: self.__printer( "HostFilesPlugin: Exception %s while trying to write env vars to guest" % (str(ex))) if isinstance(cpu, X86CPU): api.w_r(cpu_index, "EAX", -1) elif isinstance(cpu, X64CPU): api.w_r(cpu_index, "RAX", -1) else: if isinstance(cpu, X86CPU): api.w_r(cpu_index, "EAX", 0) elif isinstance(cpu, X64CPU): api.w_r(cpu_index, "RAX", 0)
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 opcodes(params, db, proc): from mw_monitor_classes import mwmon from mw_monitor_classes import is_in_pending_resolution from api import CallbackManager import api from DeviareDbParser import ArgumentParser TARGET_LONG_SIZE = api.get_os_bits() / 8 cpu_index = params["cpu_index"] cpu = params["cpu"] pc = params["cur_pc"] next_pc = params["next_pc"] # First, check if the next_pc is located in a module with # pending symbol resolution, and update symbols # accordingly if is_in_pending_resolution(proc.get_pgd(), next_pc): proc.update_symbols() try: # Locate nearest lower symbol sym = proc.locate_nearest_symbol(next_pc) if sym is None: return mod = sym.get_mod() fun = sym.get_fun() real_api_addr = sym.get_addr() # Reduce FP's by checking that the origin EIP is not also within the # same module (code reuse inside the dll) if mod in proc.modules: for (base, size) in proc.modules[mod]: if pc >= base and pc <= (base + size): return # First shortcut: check if it is an excluded api/module, or included: if mwmon.exclude_apis_addrs is not None and len(mwmon.exclude_apis_addrs) > 0: if real_api_addr in mwmon.exclude_apis_addrs: return if mwmon.exclude_modules_addrs is not None and len(mwmon.exclude_modules_addrs) > 0: for (base, size) in mwmon.exclude_modules_addrs: if real_api_addr >= base and real_api_addr < (base + size): return # Origin modules if mwmon.exclude_origin_modules_addrs is not None and len(mwmon.exclude_origin_modules_addrs) > 0: # pc is the originating pc for (base, size) in mwmon.exclude_origin_modules_addrs: if pc >= base and pc < (base + size): return if mwmon.include_apis_addrs is not None and len(mwmon.include_apis_addrs) > 0: if real_api_addr not in mwmon.include_apis_addrs: return if proc.in_mod_boundaries(real_api_addr): pgd = api.get_running_process(cpu_index) # Set callback on return address if TARGET_LONG_SIZE == 4: try: ret_addr_val = api.r_va(pgd, cpu.ESP, 4) ret_addr = struct.unpack("<I", ret_addr_val)[0] except: ret_addr = 0 mwmon.printer("Could not read return address on API tracer") elif TARGET_LONG_SIZE == 8: try: ret_addr_val = api.r_va(pgd, cpu.RSP, 8) ret_addr = struct.unpack("<Q", ret_addr_val)[0] except: ret_addr = 0 mwmon.printer("Could not read return address on API tracer") if mwmon.api_tracer_light_mode: if real_api_addr == next_pc: if TARGET_LONG_SIZE == 4: proc.add_call(pc, real_api_addr, "[PID: %x] %08x --> %s:%s(%08x) --> %08x\n" % ( proc.pid, pc, mod, fun, real_api_addr, ret_addr)) elif TARGET_LONG_SIZE == 8: proc.add_call(pc, real_api_addr, "[PID: %x] %016x --> %s:%s(%016x) --> %016x\n" % ( proc.pid, pc, mod, fun, real_api_addr, ret_addr)) else: if TARGET_LONG_SIZE == 4: proc.add_call(pc, real_api_addr, "[PID: %x] %08x --> %s:%s(+%x)(%08x) --> %08x\n" % ( proc.pid, pc, mod, fun, (next_pc - real_api_addr), next_pc, ret_addr)) elif TARGET_LONG_SIZE == 8: proc.add_call(pc, real_api_addr, "[PID: %x] %016x --> %s:%s(+%x)(%016x) --> %016x\n" % ( proc.pid, pc, mod, fun, (next_pc - real_api_addr), next_pc, ret_addr)) return data = APICallData() data.pc = pc data.mod = mod data.fun = fun data.ret_addr = ret_addr if TARGET_LONG_SIZE == 4: argument_parser = ArgumentParser(db, cpu, cpu.ESP, mod, fun) elif TARGET_LONG_SIZE == 8: argument_parser = ArgumentParser(db, cpu, cpu.RSP, mod, fun) if not argument_parser.in_db(): return data.in_args = [arg for arg in argument_parser.get_in_args()] # If return address could not be read, we skip the callback if ret_addr != 0: callback_name = "ret_bp_%d" % mwmon.bp_counter callback = functools.partial(opcodes_ret, pc, real_api_addr, data, callback_name, argument_parser, mod, fun, proc) mwmon.cm.add_callback(CallbackManager.INSN_BEGIN_CB, callback, name=callback_name, addr=data.ret_addr, pgd=pgd) mwmon.bp_counter += 1 except Exception as e: mwmon.printer(str(e)) traceback.print_exc() finally: return
def __init__(self, pgd, section_object): import api TARGET_LONG_SIZE = api.get_os_bits() / 8 # Volatility object representing the section self.section_object = section_object # Volatility lacks the vtype for _SECTION, which # has scarce documentation: # http://forum.sysinternals.com/section-object_topic24975.html # These structures seem to remain consistent # across different Windows versions. # typedef struct _MMADDRESS_NODE { # union { # LONG_PTR Balance : 2; # struct _MMADDRESS_NODE *Parent; # } u1; # struct _MMADDRESS_NODE *LeftChild; # struct _MMADDRESS_NODE *RightChild; # ULONG_PTR StartingVpn; # ULONG_PTR EndingVpn; # } MMADDRESS_NODE, *PMMADDRESS_NODE; # typedef struct _SECTION { # MMADDRESS_NODE Address; # PSEGMENT Segment; # LARGE_INTEGER SizeOfSection; # union { # ULONG LongFlags; # MMSECTION_FLAGS Flags; # } u; # MM_PROTECTION_MASK InitialPageProtection; # } SECTION, *PSECTION; # As we can see, Volatility has instead a _SECTION_OBJECT # vtype, which, consistently across Windows versions, # has at the beginning of the structure, 5 pointers, just # like the MMADDRESS_NODE for _SECTION. Therefore, the Segment # field seems to be at the same offset on both structures: # _SECTION and _SECTION_OBJECT, both for 32 and 64 bits. # Flags are located after Segment (PSEGMENT) + LARGE_INTEGER (64 bits independently of arch) # --> The offset should be the size of 6 pointers + size of LARGE_INTEGER # Flags are always 4 bytes # Compute FileBacked and CopyOnWrite try: self.flags = struct.unpack( "I", api.r_va(pgd, self.section_object.obj_offset + ((TARGET_LONG_SIZE * 6) + 8), 0x4))[0] except: self.flags = 0x00000000 mwmon.printer("Could not read flags in Section __init__") self.cow = ((self.flags & 0x00000800) != 0) self.file_backed = ((self.flags & 0x00000080) != 0) self.backing_file = None # If so, get corresponding file. if self.file_backed: # Dereference as _SEGMENT, that is different from _SEGMENT_OBJECT # This is because the volatility profile lacks the _SECTION object, # and instead has the _SECTION_OBJECT. Since the Segment field # of _SECTION and _SECTION_OBJECT are aligned, we can just dereference # that offset. Nevertheless, _SECTION_OBJECT has a _SEGMENT_OBJECT type # Segment, while _SECTION has a _SEGMENT type Segment... # http://forum.sysinternals.com/section-object_topic24975.html self.segment = self.section_object.Segment.dereference_as( "_SEGMENT") file_obj = self.segment.ControlArea.FilePointer from volatility.plugins.overlays.windows.windows import _FILE_OBJECT from volatility.obj import Pointer # on winxp file_obj is volatility.obj.Pointer with .target being _FILE_OBJECT if not (type(file_obj) is Pointer and type(file_obj.dereference()) is _FILE_OBJECT): from volatility.plugins.overlays.windows.windows import _EX_FAST_REF if type(file_obj) is _EX_FAST_REF: # on newer volatility profiles, FilePointer is _EX_FAST_REF, needs deref file_obj = file_obj.dereference_as("_FILE_OBJECT") else: raise TypeError("The type for self.segment.ControlArea.FilePointer in Section" + \ "class does not match _FILE_OBJECT or _EX_FAST_REF: %r (type %r)" % (file_obj, type(file_obj))) for fi in mwmon.data.files: if fi.file_name == str(file_obj.FileName): self.backing_file = fi break # If we have still not recorded the file, add it to files record if self.backing_file is None: self.backing_file = File(str(file_obj.FileName)) mwmon.data.files.append(self.backing_file) self.unpickled = False self.offset = self.section_object.obj_offset
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 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 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 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 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 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 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 __init__(self, proc_name): import api self.TARGET_LONG_SIZE = api.get_os_bits() / 8 self.proc_num = Process.proc_counter Process.proc_counter += 1 self.proc_name = proc_name self.pgd = 0 self.pid = 0 self.modules = {} if self.TARGET_LONG_SIZE == 4: self.min_mod_addr = 0xFFFFFFFF elif self.TARGET_LONG_SIZE == 8: self.min_mod_addr = 0xFFFFFFFFFFFFFFFF else: raise Exception( "[Process::init()] Unsupported TARGET_LONG_SIZE: %d" % self.TARGET_LONG_SIZE) self.max_mod_addr = 0x0 self.symbols = [] # Record of API calls (related to VADs, and others self.vads = [] self.other_calls = [] # Chunks of memory injected to other processes self.injections = [] self.file_operations = [] self.section_maps = [] # Keep a list with all the calls ordered self.all_calls = [] # Indicates that this instance is a result of unpicking a serialized # object self.unpickled = False # Exited. Indicates that process has already exited. self.exited = False from interproc import ntcreateprocess from interproc import ntopenprocess from interproc import ntwritevirtualmemory from interproc import ntreadvirtualmemory from interproc import ntreadfile from interproc import ntwritefile from interproc import ntmapviewofsection from interproc import ntunmapviewofsection from interproc import ntvirtualprotect from interproc import ntallocatevirtualmemory from utils import ConfigurationManager as conf_m if mwmon.interproc: # Dictionary to store breakpoints for the following APIs: self.breakpoints = {("ntdll.dll", "ZwOpenProcess"): None, ("ntdll.dll", "ZwReadFile"): None, ("ntdll.dll", "ZwWriteFile"): None, ("ntdll.dll", "ZwMapViewOfSection"): None, ("ntdll.dll", "ZwUnmapViewOfSection"): None, ("ntdll.dll", "ZwWriteVirtualMemory"): None, ("ntdll.dll", "ZwReadVirtualMemory"): None, ("ntdll.dll", "ZwProtectVirtualMemory"): None, ("ntdll.dll", "NtAllocateVirtualMemory"): None} self.bp_funcs = { ("ntdll.dll", "ZwOpenProcess"): (ntopenprocess, True and not mwmon.api_tracer and not mwmon.coverage), ("ntdll.dll", "ZwReadFile"): (ntreadfile, False and not mwmon.api_tracer and not mwmon.coverage), ("ntdll.dll", "ZwWriteFile"): (ntwritefile, False and not mwmon.api_tracer and not mwmon.coverage), ("ntdll.dll", "ZwMapViewOfSection"): (ntmapviewofsection, True and not mwmon.api_tracer and not mwmon.coverage), ("ntdll.dll", "ZwUnmapViewOfSection"): (ntunmapviewofsection, True and not mwmon.api_tracer and not mwmon.coverage), ("ntdll.dll", "ZwWriteVirtualMemory"): (ntwritevirtualmemory, False and not mwmon.api_tracer and not mwmon.coverage), ("ntdll.dll", "ZwReadVirtualMemory"): (ntreadvirtualmemory, False and not mwmon.api_tracer and not mwmon.coverage), ("ntdll.dll", "ZwProtectVirtualMemory"): (ntvirtualprotect, False and not mwmon.api_tracer and not mwmon.coverage), ("ntdll.dll", "NtAllocateVirtualMemory"): (ntallocatevirtualmemory, True and not mwmon.api_tracer and not mwmon.coverage)} profile = conf_m.vol_profile # If before vista: if "WinXP" in profile or "Win2003" in profile: # We hook both, because although Kernel32 calls the "Ex" version, a # program may call directy ZwCreateProcess self.breakpoints[("ntdll.dll", "ZwCreateProcessEx")] = None self.bp_funcs[("ntdll.dll", "ZwCreateProcessEx")] = ( ntcreateprocess, True and not mwmon.api_tracer and not mwmon.coverage) self.breakpoints[("ntdll.dll", "ZwCreateProcess")] = None self.bp_funcs[("ntdll.dll", "ZwCreateProcess")] = ( ntcreateprocess, True and not mwmon.api_tracer and not mwmon.coverage) else: self.breakpoints[("ntdll.dll", "ZwCreateProcessEx")] = None self.bp_funcs[("ntdll.dll", "ZwCreateProcessEx")] = ( ntcreateprocess, True and not mwmon.api_tracer and not mwmon.coverage) self.breakpoints[("ntdll.dll", "ZwCreateProcess")] = None self.bp_funcs[("ntdll.dll", "ZwCreateProcess")] = ( ntcreateprocess, True and not mwmon.api_tracer and not mwmon.coverage) # On Vista (and onwards), kernel32.dll no longer uses # ZwCreateProcess/ZwCreateProcessEx (although these function remain # in ntdll.dll. It Uses ZwCreateUserProcess. self.breakpoints[("ntdll.dll", "ZwCreateUserProcess")] = None self.bp_funcs[("ntdll.dll", "ZwCreateUserProcess")] = ( ntcreateprocess, True and not mwmon.api_tracer and not mwmon.coverage) else: self.breakpoints = {} self.bp_funcs = {}
# # preserve_filenames: "" # # ------------------------------------------------------------------------------- from __future__ import print_function import os import shutil import json import tarfile import tempfile import functools # Determine TARGET_LONG_SIZE from api import get_os_bits TARGET_LONG_SIZE = get_os_bits() / 8 # Script requirements requirements = ["plugins.guest_agent"] # Global variables # Callback manager cm = None # Printer pyrebox_print = None # Target process name target_procname = None target_pgd = None # Breakpoint for entry point entry_point_bp = None
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 disassemble(addr, cpu_index): ''' Disassemble instruction. Receives 2 parameters: :param addr: Address from instruction to disassemble :type addr: int :param cpu_index: CPU index :type cpu_index: int ''' global logger global num_ins pgd = api.get_running_process(cpu_index) if api.get_os_bits() == 32: md = Cs(CS_ARCH_X86, CS_MODE_32) content = api.r_va(pgd, addr, 0x4) else: md = Cs(CS_ARCH_X86, CS_MODE_64) content = api.r_va(pgd, addr, 0x6) md.detail = True for insn in md.disasm(content, addr): if insn.mnemonic == "call": if len(insn.operands) > 0: mycpu = api.r_cpu(0) simbolo = None if api.get_os_bits() == 32: simbolo = api.va_to_sym(pgd, mycpu.EIP) else: simbolo = api.va_to_sym(pgd, mycpu.RIP) if simbolo != None and api.get_os_bits() != 32: ## Microsoft x64 calling convention ## logger.info( "[API]0x%x:\t%s\t%s\t[RIP]:0x%x\t%s\t[PGD]: %x", insn.address, insn.mnemonic, insn.op_str, mycpu.RIP, simbolo, pgd) num_ins = num_ins + 1 ## RCX - Arg 1 try: rcx_content = api.r_va(pgd, mycpu.RCX, 0x100) logger.info("[RCX]: 0x%x [Data]: %s", mycpu.RCX, smart_format(rcx_content, 0x100, True)) except: logger.info("[RCX]: 0x%x", mycpu.RCX) ## RDX - Arg 2 try: rdx_content = api.r_va(pgd, mycpu.RDX, 0x100) logger.info("[RDX]: 0x%x [Data]: %s", mycpu.RDX, smart_format(rdx_content, 0x100, True)) except: logger.info("[RDX]: 0x%x", mycpu.RDX) ## R8 - Arg 3 try: r8_content = api.r_va(pgd, mycpu.R8, 0x100) logger.info("[R8]: 0x%x [Data]: %s", mycpu.R8, smart_format(r8_content, 0x100, True)) except: logger.info("[R8]: 0x%x", mycpu.R8) ## R9 - Arg 4 try: r9_content = api.r_va(pgd, mycpu.R9, 0x100) logger.info("[R9]: 0x%x [Data]: %s", mycpu.R9, smart_format(r9_content, 0x100, True)) except: logger.info("[R9]: 0x%x", mycpu.R9) ## RAX - return value try: rax_content = api.r_va(pgd, mycpu.RAX, 0x100) logger.info("[RAX]: 0x%x [Data]: %s", mycpu.RAX, smart_format(rax_content, 0x100, True)) except: logger.info("[RAX]: 0x%x", mycpu.RAX) logger.info("--") elif simbolo != None: ## x86 call conventions # cdecl -> arguments are pushed on the stack in the reverse order. EAX return # syscall -> arguments are pushed on the stack right to left. # optlink -> arguments are pushed on the stack right to left. # ... logger.info( "[API]0x%x:\t%s\t%s\t[EIP]:0x%x\t%s\t[PGD]: %x", insn.address, insn.mnemonic, insn.op_str, mycpu.EIP, simbolo, pgd) num_ins = num_ins + 1 bytestoread = 0x200 try: eax_content = api.r_va(pgd, mycpu.EAX, bytestoread) logger.info( "[EAX]: 0x%x [Data]: %s", mycpu.EAX, smart_format(eax_content, bytestoread, True)) except: logger.info("[EAX]: 0x%x", mycpu.EAX) try: ecx_content = api.r_va(pgd, mycpu.ECX, bytestoread) logger.info( "[ECX]: 0x%x [Data]: %s", mycpu.ECX, smart_format(ecx_content, bytestoread, True)) except: logger.info("[ECX]: 0x%x", mycpu.ECX) try: edx_content = api.r_va(pgd, mycpu.EDX, bytestoread) logger.info( "[EDX]: 0x%x [Data]: %s", mycpu.EDX, smart_format(edx_content, bytestoread, True)) except: logger.info("[EDX]: 0x%x", mycpu.EDX) try: ebp_arg2_content = api.r_va(pgd, mycpu.EBP + 8, bytestoread) logger.info( "[EBP+8]: 0x%x [Data]: %s", mycpu.EBP + 8, smart_format(ebp_arg2_content, bytestoread, True)) except: logger.info("[EBP+8]: 0x%x", mycpu.EBP + 8) try: ebp_arg3_content = api.r_va(pgd, mycpu.EBP + 12, bytestoread) logger.info( "[EBP+12]: 0x%x [Data]: %s", mycpu.EBP + 12, smart_format(ebp_arg3_content, bytestoread, True)) except: logger.info("[EBP+12]: 0x%x", mycpu.EBP + 12) try: ebp_arg4_content = api.r_va(pgd, mycpu.EBP + 16, bytestoread) logger.info( "[EBP+16]: 0x%x [Data]: %s", mycpu.EBP + 16, smart_format(ebp_arg4_content, bytestoread, True)) except: logger.info("[EBP+16]: 0x%x", mycpu.EBP + 16) try: ebp_arg5_content = api.r_va(pgd, mycpu.EBP + 20, bytestoread) logger.info( "[EBP+20]: 0x%x [Data]: %s", mycpu.EBP + 20, smart_format(ebp_arg5_content, bytestoread, True)) except: logger.info("[EBP+20]: 0x%x", mycpu.EBP + 20) try: ebp_arg6_content = api.r_va(pgd, mycpu.EBP + 24, bytestoread) logger.info( "[EBP+24]: 0x%x [Data]: %s", mycpu.EBP + 24, smart_format(ebp_arg6_content, bytestoread, True)) except: logger.info("[EBP+24]: 0x%x", mycpu.EBP + 24) logger.info("--")
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