def new_process(params): ''' Monitor process creation in order to start tracing the first process. ''' from DeviareDbParser import DbConnector from mw_monitor_classes import mwmon from mw_monitor_classes import mw_monitor_start_monitoring_process pid = params["pid"] pgd = params["pgd"] name = params["name"] main_proc = mwmon.data.procs[0] if main_proc.get_proc_name() is not None and ( main_proc.get_proc_name() in name or name in main_proc.get_proc_name()): mwmon.printer("Starting monitoring process %s" % name) main_proc.set_pgd(pgd) main_proc.set_pid(pid) if mwmon.api_tracer and not mwmon.api_tracer_light_mode: mwmon.printer("Loading API tracer database...") # Initialize API doc database. We need to initialize it in this thread (callback), # because sqlite limits db access to 1 thread, and the rest of callbacks should be # running on this same thread. mwmon.db = DbConnector(mwmon.api_database_path) mw_monitor_start_monitoring_process(main_proc, insert_proc=False) mwmon.cm.rm_callback("vmi_new_proc")
def new_process(params): ''' Monitor process creation in order to start tracing the first process. ''' from DeviareDbParser import DbConnector from mw_monitor_classes import mwmon from mw_monitor_classes import mw_monitor_start_monitoring_process pid = params["pid"] pgd = params["pgd"] name = params["name"] main_proc = mwmon.data.procs[0] if main_proc.get_proc_name() is not None and (main_proc.get_proc_name() in name or name in main_proc.get_proc_name()): mwmon.printer("Starting monitoring process %s" % name) main_proc.set_pgd(pgd) main_proc.set_pid(pid) if mwmon.api_tracer and not mwmon.api_tracer_light_mode: mwmon.printer("Loading API tracer database...") # Initialize API doc database. We need to initialize it in this thread (callback), # because sqlite limits db access to 1 thread, and the rest of callbacks should be # running on this same thread. mwmon.db = DbConnector(mwmon.api_database_path) mw_monitor_start_monitoring_process(main_proc, insert_proc=False) mwmon.cm.rm_callback("vmi_new_proc")
def apitracer_start_monitoring_process(proc): from mw_monitor_classes import mwmon from api import CallbackManager mwmon.printer("Initializing API tracer...") """ # E8 - call, E9,EA,EB - jmp mwmon.cm.add_callback(CallbackManager.OPCODE_RANGE_CB, functools.partial( opcodes, db=mwmon.db, proc=proc), name="opcode1_%x" % (proc.pid), start_opcode=0xE8, end_opcode=0xEB) mwmon.cm.add_trigger("opcode1_%x" % (proc.get_pid()), "triggers/trigger_opcode_user_only.so") mwmon.cm.set_trigger_var("opcode1_%x" % (proc.get_pid()), "cr3", proc.get_pgd()) """ # FF - call and jmp mwmon.cm.add_callback(CallbackManager.OPCODE_RANGE_CB, functools.partial( opcodes, db=mwmon.db, proc=proc), name="opcode2_%x" % (proc.pid), start_opcode=0xFF, end_opcode=0xFF) mwmon.cm.add_trigger("opcode2_%x" % (proc.get_pid()), "triggers/trigger_opcode_user_only.so") mwmon.cm.set_trigger_var("opcode2_%x" % (proc.get_pid()), "cr3", proc.get_pgd()) """ # 9A - call mwmon.cm.add_callback(CallbackManager.OPCODE_RANGE_CB, functools.partial( opcodes, db=mwmon.db, proc=proc), name="opcode3_%x" % (proc.pid), start_opcode=0x9A, end_opcode=0x9A) mwmon.cm.add_trigger("opcode3_%x" % (proc.get_pid()), "triggers/trigger_opcode_user_only.so") mwmon.cm.set_trigger_var("opcode3_%x" % (proc.get_pid()), "cr3", proc.get_pgd()) """ """
def mw_monitor_start_monitoring_process(new_proc, insert_proc=True): ''' This function sets up all the callbacks and structures necessary to monitor a process. :param new_proc: The process to start monitoring as a instance of Process :type new_proc: Process :param insert_proc: (Optional) Whether to insert the process in the list of monitored processes (internal to this script). :type insert_proc: bool ''' from coverage import block_executed from api_tracer import apitracer_start_monitoring_process from dumper import dumper_start_monitoring_process from api import CallbackManager import api # Insert the process, if necessary if insert_proc: mwmon.data.procs.append(new_proc) # Start monitoring the process api.start_monitoring_process(new_proc.get_pgd()) mwmon.printer("Started monitoring process with PGD %x and name %s" % (new_proc.get_pgd(), new_proc.get_proc_name())) # coverage module # Create a callback and trigger for each process if mwmon.coverage and (mwmon.coverage_procs is None or new_proc.proc_name in mwmon.coverage_procs): mwmon.cm.add_callback(CallbackManager.BLOCK_BEGIN_CB, functools.partial(block_executed, proc=new_proc), name="block_begin_coverage_%d" % new_proc.proc_num) mwmon.cm.add_trigger("block_begin_coverage_%d" % new_proc.proc_num, "triggers/trigger_block_user_only_coverage.so") mwmon.cm.set_trigger_var( "block_begin_coverage_%d" % (new_proc.proc_num), "cr3", new_proc.get_pgd()) mwmon.cm.set_trigger_var( "block_begin_coverage_%d" % (new_proc.proc_num), "proc_num", new_proc.proc_num) # Output file name, with pid mwmon.cm.set_trigger_var( "block_begin_coverage_%d" % (new_proc.proc_num), "log_name", mwmon.coverage_log_name + ".%x" % (new_proc.pid)) # api tracer module if mwmon.api_tracer and (mwmon.api_tracer_procs is None or new_proc.proc_name in mwmon.api_tracer_procs): apitracer_start_monitoring_process(new_proc) # dumper module if mwmon.dumper: dumper_start_monitoring_process(new_proc) mwmon.cm.add_callback(CallbackManager.CONTEXTCHANGE_CB, functools.partial(context_change, new_proc, new_proc.get_proc_name()), name="context_change_%x" % new_proc.get_pgd())
def dump_command(line): ''' Dump process memory with its modules and rest of VADS. Specify process name, pid or cr3 (in hex). ''' from mw_monitor_classes import mwmon from utils import find_procs if line == "": dump() else: param = line.split()[0].strip() found = find_procs(param) if len(found) == 0: mwmon.printer("Process %s not found" % param) elif len(found) == 1 or (len(found) == 2 and found[0][1] == found[1][1]): if found[0][0] == 0: # kernel process pass else: pid, pgd, pname = found[0] dump(pid=pid) else: mwmon.printer("Please specify a valid (and unique) process")
def apitracer_start_monitoring_process(proc): from mw_monitor_classes import mwmon from api import CallbackManager mwmon.printer("Initializing API tracer...") """ # E8 - call, E9,EA,EB - jmp mwmon.cm.add_callback(CallbackManager.OPCODE_RANGE_CB, functools.partial( opcodes, db=mwmon.db, proc=proc), name="opcode1_%x" % (proc.pid), start_opcode=0xE8, end_opcode=0xEB) mwmon.cm.add_trigger("opcode1_%x" % (proc.get_pid()), "triggers/trigger_opcode_user_only.so") mwmon.cm.set_trigger_var("opcode1_%x" % (proc.get_pid()), "cr3", proc.get_pgd()) """ # FF - call and jmp mwmon.cm.add_callback(CallbackManager.OPCODE_RANGE_CB, functools.partial(opcodes, db=mwmon.db, proc=proc), name="opcode2_%x" % (proc.pid), start_opcode=0xFF, end_opcode=0xFF) mwmon.cm.add_trigger("opcode2_%x" % (proc.get_pid()), "triggers/trigger_opcode_user_only.so") mwmon.cm.set_trigger_var("opcode2_%x" % (proc.get_pid()), "cr3", proc.get_pgd()) """ # 9A - call mwmon.cm.add_callback(CallbackManager.OPCODE_RANGE_CB, functools.partial( opcodes, db=mwmon.db, proc=proc), name="opcode3_%x" % (proc.pid), start_opcode=0x9A, end_opcode=0x9A) mwmon.cm.add_trigger("opcode3_%x" % (proc.get_pid()), "triggers/trigger_opcode_user_only.so") mwmon.cm.set_trigger_var("opcode3_%x" % (proc.get_pid()), "cr3", proc.get_pgd()) """ """
def interproc_basic_stats(): from mw_monitor_classes import mwmon try: for proc in mwmon.data.procs: proc.print_stats(mwmon.interproc_basic_stats_name) except Exception: traceback.print_exc() mwmon.printer(traceback.print_stack())
def remove_process(pid, pgd, name): from mw_monitor_classes import mwmon from api import unload_module for proc in mwmon.data.procs: if proc.get_pid() == pid and proc.has_exited() is False: proc.set_exited() mwmon.printer("Process %s (%x) exited" % (name, pid))
def serialize_interproc(): from mw_monitor_classes import mwmon try: f_out = open(mwmon.interproc_bin_log_name, "w") pickle.dump(mwmon.data, f_out) f_out.close() except Exception: traceback.print_exc() mwmon.printer(traceback.print_stack())
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 update_symbols(self): import api from api import CallbackManager global mods_pending_symbol_resolution if self.unpickled: return syms = api.get_symbol_list() # Check if we can remove the module from the list of modules with # pending symbol resolution for mod in api.get_module_list(self.get_pgd()): if mod["symbols_resolved"] and mod["name"] in mods_pending_symbol_resolution.get(self.get_pgd(), []): del mods_pending_symbol_resolution[self.get_pgd()][mod["name"]] for d in syms: mod = d["mod"] fun = d["name"] addr = d["addr"] pos = bisect.bisect_left(self.symbols, Symbol("", "", addr)) if pos >= 0 and pos < len(self.symbols) and self.symbols[pos].get_addr() == addr: continue if mod in self.modules: for pair in self.modules[mod]: # Update the (include/exclude addresses in apitracer) if mwmon.include_apis is not None: if (mod.lower(), fun.lower()) in mwmon.include_apis: if (pair[0] + addr) not in mwmon.include_apis_addrs: mwmon.include_apis_addrs.append(pair[0] + addr) if mwmon.exclude_apis is not None: if (mod.lower(), fun.lower()) in mwmon.exclude_apis: if (pair[0] + addr) not in mwmon.exclude_apis_addrs: mwmon.exclude_apis_addrs.append(pair[0] + addr) bisect.insort( self.symbols, Symbol(mod, fun, pair[0] + addr)) if mwmon.interproc or mwmon.api_tracer or mwmon.dumper: # Add breakpoint if necessary if (mod, fun) in self.breakpoints and self.breakpoints[(mod, fun)] is None: f_callback = self.bp_funcs[(mod, fun)][0] update_vads = self.bp_funcs[(mod, fun)][1] callback = functools.partial( f_callback, pid=self.pid, proc=self, update_vads=update_vads) bp = mwmon.cm.add_callback(CallbackManager.INSN_BEGIN_CB, callback, name="api_bp_%x_%s" % (self.pid, fun), addr=pair[0] + addr, pgd=self.pgd) self.breakpoints[(mod, fun)] = (bp, pair[0] + addr) mwmon.printer("Adding breakpoint at %s:%s %x:%x from process with PID %x" % (mod, fun, pair[0] + addr, self.pgd, self.pid))
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 remove_process(params): from mw_monitor_classes import mwmon from api import unload_module pid = params["pid"] pgd = params["pgd"] name = params["name"] for proc in mwmon.data.procs: if proc.get_pid() == pid and proc.has_exited() is False: proc.set_exited() mwmon.printer("Process %s (%x) exited" % (name, pid))
def mw_monitor_start_monitoring_process(new_proc, insert_proc=True): ''' This function sets up all the callbacks and structures necessary to monitor a process. :param new_proc: The process to start monitoring as a instance of Process :type new_proc: Process :param insert_proc: (Optional) Whether to insert the process in the list of monitored processes (internal to this script). :type insert_proc: bool ''' from coverage import block_executed from api_tracer import apitracer_start_monitoring_process from dumper import dumper_start_monitoring_process from api import CallbackManager import api # Insert the process, if necessary if insert_proc: mwmon.data.procs.append(new_proc) # Start monitoring the process api.start_monitoring_process(new_proc.get_pgd()) mwmon.printer("Started monitoring process with PGD %x and name %s" % (new_proc.get_pgd(), new_proc.get_proc_name())) # coverage module # Create a callback and trigger for each process if mwmon.coverage and (mwmon.coverage_procs is None or new_proc.proc_name in mwmon.coverage_procs): mwmon.cm.add_callback(CallbackManager.BLOCK_BEGIN_CB, functools.partial( block_executed, proc=new_proc), name="block_begin_coverage_%d" % new_proc.proc_num) mwmon.cm.add_trigger("block_begin_coverage_%d" % new_proc.proc_num, "triggers/trigger_block_user_only_coverage.so") mwmon.cm.set_trigger_var("block_begin_coverage_%d" % (new_proc.proc_num), "cr3", new_proc.get_pgd()) mwmon.cm.set_trigger_var("block_begin_coverage_%d" % (new_proc.proc_num), "proc_num", new_proc.proc_num) # Output file name, with pid mwmon.cm.set_trigger_var("block_begin_coverage_%d" % (new_proc.proc_num), "log_name", mwmon.coverage_log_name + ".%x" % (new_proc.pid)) # api tracer module if mwmon.api_tracer and (mwmon.api_tracer_procs is None or new_proc.proc_name in mwmon.api_tracer_procs): apitracer_start_monitoring_process(new_proc) # dumper module if mwmon.dumper: dumper_start_monitoring_process(new_proc) mwmon.cm.add_callback(CallbackManager.CONTEXTCHANGE_CB, functools.partial(context_change, new_proc, new_proc.get_proc_name()), name="context_change_%x" % new_proc.get_pgd())
def context_change(new_proc, target_mod_name, params): '''Callback triggered for every context change''' global ntdll_breakpoint from mw_monitor_classes import mwmon from api import BP import api from api import CallbackManager from functools import partial old_pgd = params["old_pgd"] new_pgd = params["new_pgd"] if new_proc.get_pgd() == new_pgd: ep = find_ep(new_proc, target_mod_name) if ep is not None: mwmon.printer("The entry point for %s is %x\n" % (target_mod_name, ep)) mwmon.cm.rm_callback("context_change_%x" % new_proc.get_pgd()) try: # Load modules and symbols for the process mods = api.get_module_list(new_proc.get_pgd()) if mods is not None: for m in mods: name = m["name"] base = m["base"] size = m["size"] new_proc.set_module(name, base, size) # NTDLL is a special case, and we set a breakpoint # on the code of the main module to trigger the symbol resolution # as soon as we execute one instruction in its # region if target_mod_name in name: ntdll_breakpoint[new_proc.get_pgd()] = BP( base, new_proc.get_pgd(), size=size, func=partial(ntdll_breakpoint_func, new_proc), new_style=True) ntdll_breakpoint[new_proc.get_pgd()].enable() except ValueError as e: # The process has not been created yet, so we need to # wait for symbol resolution pass # Callback for each module loaded mwmon.cm.add_callback(CallbackManager.LOADMODULE_CB, module_loaded, pgd=new_proc.get_pgd(), name=("load_module_%x" % new_proc.get_pgd()))
def context_change(new_proc, target_mod_name, params): '''Callback triggered for every context change''' global ntdll_breakpoint from mw_monitor_classes import mwmon from api import BP import api from api import CallbackManager from functools import partial old_pgd = params["old_pgd"] new_pgd = params["new_pgd"] if new_proc.get_pgd() == new_pgd: ep = find_ep(new_proc, target_mod_name) if ep is not None: mwmon.printer("The entry point for %s is %x\n" % (target_mod_name, ep)) mwmon.cm.rm_callback("context_change_%x" % new_proc.get_pgd()) try: # Load modules and symbols for the process mods = api.get_module_list(new_proc.get_pgd()) if mods is not None: for m in mods: name = m["name"] base = m["base"] size = m["size"] new_proc.set_module(name, base, size) # NTDLL is a special case, and we set a breakpoint # on the code of the main module to trigger the symbol resolution # as soon as we execute one instruction in its # region if target_mod_name in name: ntdll_breakpoint[new_proc.get_pgd()] = BP(base, new_proc.get_pgd(), size = size, func = partial(ntdll_breakpoint_func, new_proc), new_style = True) ntdll_breakpoint[new_proc.get_pgd()].enable() except ValueError as e: # The process has not been created yet, so we need to # wait for symbol resolution pass # Callback for each module loaded mwmon.cm.add_callback(CallbackManager.LOADMODULE_CB, module_loaded, pgd = new_proc.get_pgd(), name = ("load_module_%x" % new_proc.get_pgd()))
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, 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 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 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 __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 find_ep(proc, proc_name): '''Given an address space and a process name, uses pefile module to get its entry point ''' import api import pefile from mw_monitor_classes import mwmon try: for m in api.get_module_list(proc.get_pgd()): name = m["name"] base = m["base"] # size = m["size"] if name == proc_name: pe_data = api.r_va(proc.get_pgd(), base, 0x1000) pe = pefile.PE(data=pe_data) ep = pe.OPTIONAL_HEADER.AddressOfEntryPoint return (base + ep) except Exception as e: mwmon.printer("Unable to run pefile on loaded module %s (%s)" % (proc_name, str(e))) pass return None
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 find_ep(proc, proc_name): '''Given an address space and a process name, uses pefile module to get its entry point ''' import api import pefile from mw_monitor_classes import mwmon try: for m in api.get_module_list(proc.get_pgd()): name = m["name"] base = m["base"] # size = m["size"] if proc_name in name: pe_data = api.r_va(proc.get_pgd(), base, 0x1000) pe = pefile.PE(data=pe_data) ep = pe.OPTIONAL_HEADER.AddressOfEntryPoint return (base + ep) except Exception as e: mwmon.printer("Unable to run pefile on loaded module %s (%s)" % (proc_name, str(e))) pass return None
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 clean(): ''' Clean up everything. At least you need to place this clean() call to the callback manager, that will unregister all the registered callbacks. ''' from mw_monitor_logging import log_calls from mw_monitor_classes import mwmon # import tarfile mwmon.printer("Cleaning module") mwmon.cm.clean() mwmon.printer("Cleaned module") out_bundle = tarfile.open(mwmon.output_bundle_name, "w:gz") if os.path.isfile('mw_monitor_run.json'): out_bundle.add('mw_monitor_run.json') if mwmon.api_tracer and mwmon.api_tracer_text_log: mwmon.printer("Writing text call log") log_calls() if os.path.isfile(mwmon.api_tracer_text_log_name): out_bundle.add(mwmon.api_tracer_text_log_name) if mwmon.api_tracer and mwmon.api_tracer_bin_log: mwmon.printer("Writing binary call log") serialize_calls() if os.path.isfile(mwmon.api_tracer_bin_log_name): out_bundle.add(mwmon.api_tracer_bin_log_name) if mwmon.interproc and mwmon.interproc_bin_log: mwmon.printer("Writing interproc bin log") serialize_interproc() if os.path.isfile(mwmon.interproc_bin_log_name): out_bundle.add(mwmon.interproc_bin_log_name) if mwmon.interproc and mwmon.interproc_basic_stats: mwmon.printer("Writing interproc text log") interproc_basic_stats() if os.path.isfile(mwmon.interproc_basic_stats_name): out_bundle.add(mwmon.interproc_basic_stats_name) if mwmon.interproc_text_log_handle is not None: mwmon.interproc_text_log_handle.close() mwmon.interproc_text_log_handle = None if os.path.isfile(mwmon.interproc_text_log_name): out_bundle.add(mwmon.interproc_text_log_name) if mwmon.coverage: mwmon.printer("Processing coverage") log_coverage() if os.path.isfile(mwmon.coverage_log_name): out_bundle.add(mwmon.coverage_log_name) if os.path.isfile(mwmon.coverage_text_name): out_bundle.add(mwmon.coverage_text_name) if mwmon.dumper: mwmon.printer("Adding dumped memory") # Add the dumped stuff. if os.path.isdir(mwmon.dumper_path): mwmon.printer(mwmon.dumper_path) out_bundle.add(mwmon.dumper_path) # Remove the temporary extracted files if os.path.isdir(mwmon.extracted_files_path): shutil.rmtree(mwmon.extracted_files_path) out_bundle.close() mwmon.printer("Module unloaded")
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 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 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 clean(): ''' Clean up everything. At least you need to place this clean() call to the callback manager, that will unregister all the registered callbacks. ''' from mw_monitor_logging import log_calls from mw_monitor_classes import mwmon # import tarfile mwmon.printer("Cleaning module") mwmon.cm.clean() mwmon.printer("Cleaned module") out_bundle = tarfile.open(mwmon.output_bundle_name,"w:gz") if os.path.isfile('mw_monitor_run.json'): out_bundle.add('mw_monitor_run.json') if mwmon.api_tracer and mwmon.api_tracer_text_log: mwmon.printer("Writing text call log") log_calls() if os.path.isfile(mwmon.api_tracer_text_log_name): out_bundle.add(mwmon.api_tracer_text_log_name) if mwmon.api_tracer and mwmon.api_tracer_bin_log: mwmon.printer("Writing binary call log") serialize_calls() if os.path.isfile(mwmon.api_tracer_bin_log_name): out_bundle.add(mwmon.api_tracer_bin_log_name) if mwmon.interproc and mwmon.interproc_bin_log: mwmon.printer("Writing interproc bin log") serialize_interproc() if os.path.isfile(mwmon.interproc_bin_log_name): out_bundle.add(mwmon.interproc_bin_log_name) if mwmon.interproc and mwmon.interproc_basic_stats: mwmon.printer("Writing interproc text log") interproc_basic_stats() if os.path.isfile(mwmon.interproc_basic_stats_name): out_bundle.add(mwmon.interproc_basic_stats_name) if mwmon.interproc_text_log_handle is not None: mwmon.interproc_text_log_handle.close() mwmon.interproc_text_log_handle = None if os.path.isfile(mwmon.interproc_text_log_name): out_bundle.add(mwmon.interproc_text_log_name) if mwmon.coverage: mwmon.printer("Processing coverage") log_coverage() if os.path.isfile(mwmon.coverage_log_name): out_bundle.add(mwmon.coverage_log_name) if os.path.isfile(mwmon.coverage_text_name): out_bundle.add(mwmon.coverage_text_name) if mwmon.dumper: mwmon.printer("Adding dumped memory") # Add the dumped stuff. if os.path.isdir(mwmon.dumper_path): mwmon.printer(mwmon.dumper_path) out_bundle.add(mwmon.dumper_path) # Remove the temporary extracted files if os.path.isdir(mwmon.extracted_files_path): shutil.rmtree(mwmon.extracted_files_path) out_bundle.close() mwmon.printer("Module unloaded")
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 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 __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 if type(file_obj) is not _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") 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 generate_arg(self, arg_name, arg_typ, arg_class, is_out, addr=None, val=None, arg_num=0): # Return None for null pointers if addr is not None and addr == 0: return None # TODO - FIX - We need to correct the arg_class, because it seems that # the db has some errors while arg_class >= 65536: arg_class -= 65536 if arg_class == NKT_DBOBJCLASS_Fundamental: return BasicArgument(arg_name, arg_typ, pgd=self.pgd, addr=addr, val=val, is_out=is_out, arg_num=arg_num) elif arg_class == NKT_DBOBJCLASS_Struct: self.c.execute( "select Id,Name,Size,Align,Flags from Structs where Id = %d" % (arg_typ)) res = self.c.fetchone() if res is not None: new_struct = Struct(arg_name, res[0], res[1], res[2], res[3], res[4], pgd=self.pgd, addr=addr, val=val, is_out=is_out, arg_num=arg_num) if addr is not None: self.c.execute("select StructId,Id,Name,Offset,Bits,Flags,TypeId,TypeClass" + " from StructsMembers where StructId = %d order by Id ASC" % (arg_typ)) sub_fields = self.c.fetchall() for sub_field in sub_fields: # Skip incorrect blank fields in the db if sub_field[3] == 0 and sub_field[1] > 1: continue offset = sub_field[3] / 8 new_struct.add_field(offset, self.generate_arg(sub_field[2], sub_field[6], sub_field[7], is_out, addr=addr + offset, val=None, arg_num=arg_num)) else: mwmon.printer("Unsupported type: A struct has been returned as" + "return value (EAX/RAX), or as register parameter (RCX/RDX/R8/R9).") return new_struct else: return None elif arg_class == NKT_DBOBJCLASS_Union: self.c.execute( "select Id,Name,Size,Align,Flags from Unions where Id = %d" % (arg_typ)) res = self.c.fetchone() if res is not None: new_union = Union(arg_name, res[0], res[1], res[2], res[3], res[4], pgd=self.pgd, addr=addr, val=val, is_out=is_out, arg_num=arg_num) if addr is not None: self.c.execute("select UnionId,Id,Name,Offset,Bits,Flags,TypeId,TypeClass " + "from UnionsMembers where UnionId = %d order by Id ASC" % (arg_typ)) sub_fields = self.c.fetchall() for sub_field in sub_fields: offset = sub_field[3] / 8 new_union.add_field(offset, self.generate_arg(sub_field[2], sub_field[6], sub_field[7], is_out, addr=addr + offset, arg_num=arg_num)) else: mwmon.printer("Unsupported type: A union has been returned as" + "return value (EAX/RAX), or as register parameter (RCX/RDX/R8/R9).") return new_union else: return None elif arg_class == NKT_DBOBJCLASS_Typedef: self.c.execute( "select Id,Name,TypeId,TypeClass from TypeDefs where Id = %d" % (arg_typ)) res = self.c.fetchone() if res is not None: t = Typedef(arg_name, res[0], self.generate_arg(res[1], res[2], res[3], is_out, addr=addr, val=val, arg_num=arg_num), addr=addr, val=val, is_out=is_out, arg_num=arg_num) return t else: return None elif arg_class == NKT_DBOBJCLASS_Array: self.c.execute( "select Id,Max,Size,Align,TypeId,TypeClass from Arrays where Id = %d" % (arg_typ)) res = self.c.fetchone() if res is not None: the_arr = Array(arg_name, res[0], res[1], res[2], res[3], pgd=self.pgd, addr=addr, val=val, is_out=is_out, arg_num=arg_num) if addr is not None: size_of_element = res[2] / res[1] for i in range(0, res[1]): the_arr.add_field(i, self.generate_arg("", res[4], res[5], is_out, addr=addr, val=None, arg_num=arg_num)) addr += size_of_element else: mwmon.printer("Unsupported type: An array has been returned as" + "return value (EAX/RAX), or as register parameter (RCX/RDX/R8/R9).") return the_arr else: return None elif arg_class == NKT_DBOBJCLASS_Pointer: self.c.execute( "select Id,Size,Align,TypeId,TypeClass from Pointers where Id = %d" % (arg_typ)) res = self.c.fetchone() if res is not None: # We let the Pointer class determine if it should dereference # or not the pointer the_pointer = Pointer(arg_name, arg_typ, res[1], res[2], res[3], res[4], is_out, pgd=self.pgd, addr=addr, val=val, arg_num=arg_num) return the_pointer else: return None elif arg_class == NKT_DBOBJCLASS_Reference: self.c.execute( "select Id,Size,Align,TypeId,TypeClass from XReferences where Id = %d" % (arg_typ)) res = self.c.fetchone() if res is not None: if addr is not None: # Dereference pointer. we can safely dereference it when the argument parser is created (function # call) because the address will not change when the # function returns if it is an output parameter. deref_addr = struct.unpack("<I", read(self.pgd, addr, 4))[0] if res[ 1] == 32 else struct.unpack("<Q", read(self.pgd, addr, 8))[0] else: deref_addr = val the_pointer = Reference(arg_name, arg_typ, res[1], res[2], self.generate_arg("", res[3], res[4], is_out, addr=deref_addr, arg_num=arg_num), pgd=self.pgd, addr=addr, val=val, arg_num=arg_num) return the_pointer else: return None elif arg_class == NKT_DBOBJCLASS_Enumeration: self.c.execute( "select Id,Name,Size,Align from Enumerations where Id = %d" % (arg_typ)) res = self.c.fetchone() if res is not None: return Enumeration(arg_name, res[0], None, res[2], pgd=self.pgd, addr=addr, val=val, is_out=is_out, arg_num=arg_num) else: return None
def dumper_start_monitoring_process(new_proc): ''' Initialization function called for every new process created. ''' from mw_monitor_classes import mwmon from api import CallbackManager if mwmon.dumper_onexit: dll, fun = ("ntdll.dll", "ZwTerminateProcess") # Add a bp to the list of symbol based breakpoints for the process new_proc.breakpoints[(dll, fun)] = None new_proc.bp_funcs[(dll, fun)] = (functools.partial(dump, terminate_process=True), False) mwmon.printer("Deferring dumper breakpoint at %s:%s (PGD: %x)" % (dll, fun, new_proc.get_pgd())) dump_at = mwmon.dumper_dumpat if dump_at is not None: # Possible formats for dump_at: # 0x00400000 # user32.dll!CharNextW # user32.dll!CharNextW!0x00400000 terms = dump_at.split("!") if len(terms) == 1: try: addr = int(terms[0], 16) except Exception as e: mwmon.printer( "Dumper - dump_at: Wrong address value, must specify an hex number" ) return cb_name = "dumper_bp_%x_%x" % (new_proc.get_pid(), addr) bp = mwmon.cm.add_callback(CallbackManager.INSN_BEGIN_CB, functools.partial( dump, pid=new_proc.pid, callback_name=cb_name), name=cb_name, addr=addr, pgd=new_proc.get_pgd()) breakpoints.append(bp) mwmon.printer("Adding dumper breakpoint at %x (CR3: %x)" % (addr, new_proc.get_pgd())) elif len(terms) == 2: dll = terms[0] fun = terms[1] # Add a bp to the list of symbol based breakpoints for the process if (dll, fun) in new_proc.breakpoints: mwmon.printer( "Cannot set dump callback on standard function %s" % fun) return new_proc.breakpoints[(dll, fun)] = None new_proc.bp_funcs[(dll, fun)] = (dump, False) mwmon.printer("Deferring dumper breakpoint at %s:%s (PGD: %x)" % (dll, fun, new_proc.get_pgd())) elif len(terms) == 3: dll = terms[0] fun = terms[1] try: from_addr = int(terms[2], 16) except Exception as e: mwmon.printer( "Dumper - dump_at: Wrong address value, must specify an hex number : %s" % str(e)) return # Add a bp to the list of symbol based breakpoints for the process if (dll, fun) in new_proc.breakpoints: mwmon.printer( "Cannot set dump callback on standard function %s" % fun) return new_proc.breakpoints[(dll, fun)] = None new_proc.bp_funcs[(dll, fun)] = (functools.partial(dump, from_addr=from_addr), False) mwmon.printer( "Deferring dumper breakpoint at %s:%s from %x (PGD: %x)" % (dll, fun, from_addr, new_proc.get_pgd())) else: mwmon.printer( "Incorrect format for dumper dump_at parameter. No hook will be created" )
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 dumper_start_monitoring_process(new_proc): ''' Initialization function called for every new process created. ''' from mw_monitor_classes import mwmon from api import CallbackManager if mwmon.dumper_onexit: dll, fun = ("ntdll.dll", "ZwTerminateProcess") # Add a bp to the list of symbol based breakpoints for the process new_proc.breakpoints[(dll, fun)] = None new_proc.bp_funcs[(dll, fun)] = ( functools.partial(dump, terminate_process=True), False) mwmon.printer("Deferring dumper breakpoint at %s:%s (PGD: %x)" % (dll, fun, new_proc.get_pgd())) dump_at = mwmon.dumper_dumpat if dump_at is not None: # Possible formats for dump_at: # 0x00400000 # user32.dll!CharNextW # user32.dll!CharNextW!0x00400000 terms = dump_at.split("!") if len(terms) == 1: try: addr = int(terms[0], 16) except Exception as e: mwmon.printer( "Dumper - dump_at: Wrong address value, must specify an hex number") return cb_name = "dumper_bp_%x_%x" % (new_proc.get_pid(), addr) bp = mwmon.cm.add_callback(CallbackManager.INSN_BEGIN_CB, functools.partial( dump, pid=new_proc.pid, callback_name=cb_name), name=cb_name, addr=addr, pgd=new_proc.get_pgd()) breakpoints.append(bp) mwmon.printer("Adding dumper breakpoint at %x (CR3: %x)" % (addr, new_proc.get_pgd())) elif len(terms) == 2: dll = terms[0] fun = terms[1] # Add a bp to the list of symbol based breakpoints for the process if (dll, fun) in new_proc.breakpoints: mwmon.printer( "Cannot set dump callback on standard function %s" % fun) return new_proc.breakpoints[(dll, fun)] = None new_proc.bp_funcs[(dll, fun)] = (dump, False) mwmon.printer("Deferring dumper breakpoint at %s:%s (PGD: %x)" % (dll, fun, new_proc.get_pgd())) elif len(terms) == 3: dll = terms[0] fun = terms[1] try: from_addr = int(terms[2], 16) except Exception as e: mwmon.printer( "Dumper - dump_at: Wrong address value, must specify an hex number : %s" % str(e)) return # Add a bp to the list of symbol based breakpoints for the process if (dll, fun) in new_proc.breakpoints: mwmon.printer( "Cannot set dump callback on standard function %s" % fun) return new_proc.breakpoints[(dll, fun)] = None new_proc.bp_funcs[(dll, fun)] = ( functools.partial(dump, from_addr=from_addr), False) mwmon.printer("Deferring dumper breakpoint at %s:%s from %x (PGD: %x)" % (dll, fun, from_addr, new_proc.get_pgd())) else: mwmon.printer("Incorrect format for dumper dump_at parameter. No hook will be created")
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)
of.close() # Dump every dll. mods = dict( (mod.DllBase.v(), mod) for mod in task.get_load_modules()) # List of covered_ranges contains all the address ranges already dumped, to avoid # dumping them as vads. covered_ranges = [task.Peb.ImageBaseAddress] for mod in mods.values(): mod_base = mod.DllBase.v() mod_name = mod.BaseDllName if not task_space.is_valid_address(mod_base): mwmon.printer( "Error: DllBase is unavailable (possibly due to paging)" ) continue else: mwmon.printer("Dumping module %s for %x" % (mod_name, task.UniqueProcessId)) dump_file = os.path.join( path, "module.{0:x}.{1:x}.dll".format( task.UniqueProcessId, mod_base)) of = open(dump_file, 'wb') pe_file = obj.Object("_IMAGE_DOS_HEADER", offset=mod_base, vm=task_space) covered_ranges.append(mod_base) try: for offset, code in pe_file.get_image(unsafe=True,
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 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 initialize_callbacks(module_hdl, printer): ''' Initilize callbacks for this module. This function will be triggered whenever import_module command is triggered. ''' from mw_monitor_classes import Process from mw_monitor_classes import mwmon import dumper import api from ipython_shell import add_command from plugins.guest_agent import guest_agent # Update printer mwmon.printer = printer # Read configuration mwmon.printer("Reading mw_monitor configuration...") # Config parser for main static configuration file config = ConfigParser.RawConfigParser() config.read('mw_monitor.conf') # Read run configuration from json file f = open("mw_monitor_run.json","r") config_run = json.load(f) f.close() # GENERAL CONFIGURATION if "files_path" not in config_run["general"] or \ "main_executable" not in config_run["general"] or \ "files_bundle" not in config_run["general"]: raise ValueError("File to run not properly specified") mwmon.output_bundle_name = config.get('general', 'output_bundle') mwmon.files_path = config_run['general']['files_path'] mwmon.main_executable = config_run['general']['main_executable'] mwmon.files_bundle = config_run['general']['files_bundle'] mwmon.api_database_path = config.get('general', 'api_database') # Set up process copy and execution mwmon.printer("Copying host file to guest, using agent...") #Extract files in a temporary directory extracted_files_path = tempfile.mkdtemp() mwmon.extracted_files_path = extracted_files_path zip_ref = zipfile.ZipFile(mwmon.files_bundle, 'r') zip_ref.extractall(extracted_files_path) zip_ref.close() onlyfiles = [f for f in os.listdir(extracted_files_path) if os.path.isfile(os.path.join(extracted_files_path, f))] #Copy the files to the guest for f in onlyfiles: guest_agent.copy_file(os.path.join(extracted_files_path,f),os.path.join(mwmon.files_path,f)) ex_path = str(ntpath.join(mwmon.files_path,mwmon.main_executable)) # Instruct file execution guest_agent.execute_file(ex_path) # Stop agent # guest_agent.stop_agent() # MODULE CONFIGURATION mwmon.api_tracer = config_run['modules']['api_tracer'] mwmon.interproc = config_run['modules']['interproc'] mwmon.coverage = config_run['modules']['coverage'] mwmon.dumper = config_run['modules']['dumper'] # API TRACER CONFIGURATION if mwmon.api_tracer and "api_tracer" in config_run: # Static config mwmon.api_tracer_text_log_name = config.get( 'api_tracer', 'text_log_name') mwmon.api_tracer_bin_log_name = config.get( 'api_tracer', 'bin_log_name') # Run config mwmon.api_tracer_light_mode = config_run['api_tracer']['light_mode'] mwmon.api_tracer_text_log = config_run['api_tracer']['text_log'] mwmon.api_tracer_bin_log = config_run['api_tracer']['bin_log'] if "include_apis" in config_run["api_tracer"]: mwmon.include_apis = [] mwmon.include_apis_addrs = [] for api_call in config_run["api_tracer"]["include_apis"]: try: mod, fun = api_call.split("!") mwmon.include_apis.append((mod.lower(), fun.lower())) except Exception: # Just pass over the malformed api names pass else: mwmon.include_apis = None if "exclude_apis" in config_run["api_tracer"]: mwmon.exclude_apis = [] mwmon.exclude_apis_addrs = [] for api_call in config_run["api_tracer"]["exclude_apis"]: try: mod, fun = api_call.split("!") mwmon.exclude_apis.append((mod.lower(), fun.lower())) except Exception: # Just pass over the malformed api names pass else: mwmon.excludeapis = None if "procs" in config_run["api_tracer"]: mwmon.api_tracer_procs = config_run["api_tracer"]["procs"] else: mwmon.api_tracer_procs = None if "exclude_modules" in config_run["api_tracer"]: mwmon.exclude_modules_addrs = [] mwmon.exclude_modules = [s.lower() for s in config_run["api_tracer"]["exclude_modules"]] else: mwmon.exclude_modules = None if "exclude_origin_modules" in config_run["api_tracer"]: mwmon.exclude_origin_modules_addrs = [] mwmon.exclude_origin_modules = [s.lower() for s in config_run["api_tracer"]["exclude_origin_modules"]] else: mwmon.exclude_origin_modules = None mwmon.exclude_origin_modules_addrs = None # interproc configuration if mwmon.interproc: # Static config mwmon.interproc_bin_log_name = config.get('interproc', 'bin_log_name') mwmon.interproc_text_log_name = config.get( 'interproc', 'text_log_name') mwmon.interproc_basic_stats_name = config.get( 'interproc', 'basic_stats_name') # Run config mwmon.interproc_bin_log = config_run['interproc']['bin_log'] mwmon.interproc_text_log = config_run['interproc']['text_log'] mwmon.interproc_basic_stats = config_run['interproc']['basic_stats'] if mwmon.interproc_text_log: mwmon.interproc_text_log_handle = open( mwmon.interproc_text_log_name, "w") if mwmon.coverage: # Static config mwmon.coverage_log_name = config.get('coverage', 'cov_log_name') mwmon.coverage_text_name = config.get('coverage', 'cov_text_name') # Run config if "procs" in config_run["coverage"]: mwmon.coverage_procs = config_run["coverage"]["procs"] else: mwmon.coverage_procs = None # Static config mwmon.dumper_path = config.get('dumper', 'path') # DUMPER CONFIGURATION if mwmon.dumper: if os.path.isdir(mwmon.dumper_path): shutil.rmtree(mwmon.dumper_path) os.makedirs(mwmon.dumper_path) # Run config mwmon.dumper_onexit = config_run['dumper']['dump_on_exit'] # Possible formats for dump_at: # 0x00400000 # user32.dll!CharNextW # user32.dll!CharNextW!0x00400000 if "dump_at" in config_run["dumper"]: mwmon.dumper_dumpat = config_run['dumper']['dump_at'] mwmon.printer("Initializing callbacks") mwmon.cm = CallbackManager(module_hdl, new_style = True) # Initialize first process proc_name = mwmon.main_executable mwmon.data.procs = [Process(proc_name)] procs = api.get_process_list() match_procs = [] for proc in procs: name = proc["name"] pid = proc["pid"] pgd = proc["pgd"] if proc_name is not None and (proc_name in name or name in proc_name): match_procs.append((pid, pgd, name)) if len(match_procs) == 0: mwmon.printer( "No process matching that process name, deferring process detection") mwmon.printer("Initializing process creation callback") # Monitor creation of new processes, to start tracing the first one. mwmon.cm.add_callback( CallbackManager.CREATEPROC_CB, new_process, name="vmi_new_proc") elif len(match_procs) == 1: mwmon.printer( "Process found with the name specified, monitoring process...") new_process({"pid": match_procs[0][0], "pgd": match_procs[0][1], "name": match_procs[0][2]}) else: mwmon.printer( "Too many procs matching that name, please narrow down!!") if mwmon.dumper: mwmon.printer("Adding dumper commands") # Create and activate new command (dump_mwmon) add_command("dump_mwmon", dumper.dump_command) # Add a callback on process remove, to know when # we dont have any monitored process left. mwmon.cm.add_callback( CallbackManager.REMOVEPROC_CB, remove_process, name="mwmon_vmi_remove_proc") mwmon.printer("Initialized callbacks")
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 initialize_callbacks(module_hdl, printer): ''' Initilize callbacks for this module. This function will be triggered whenever import_module command is triggered. ''' from mw_monitor_classes import Process from mw_monitor_classes import mwmon import dumper import api from ipython_shell import add_command from plugins.guest_agent import guest_agent # Update printer mwmon.printer = printer # Read configuration mwmon.printer("Reading mw_monitor configuration...") # Config parser for main static configuration file config = ConfigParser.RawConfigParser() config.read('mw_monitor.conf') # Read run configuration from json file f = open("mw_monitor_run.json", "r") config_run = json.load(f) f.close() # GENERAL CONFIGURATION if "files_path" not in config_run["general"] or \ "main_executable" not in config_run["general"] or \ "files_bundle" not in config_run["general"]: raise ValueError("File to run not properly specified") mwmon.output_bundle_name = config.get('general', 'output_bundle') mwmon.files_path = config_run['general']['files_path'] mwmon.main_executable = config_run['general']['main_executable'] mwmon.files_bundle = config_run['general']['files_bundle'] mwmon.api_database_path = config.get('general', 'api_database') # Set up process copy and execution mwmon.printer("Copying host file to guest, using agent...") #Extract files in a temporary directory extracted_files_path = tempfile.mkdtemp() mwmon.extracted_files_path = extracted_files_path zip_ref = zipfile.ZipFile(mwmon.files_bundle, 'r') zip_ref.extractall(extracted_files_path) zip_ref.close() onlyfiles = [ f for f in os.listdir(extracted_files_path) if os.path.isfile(os.path.join(extracted_files_path, f)) ] #Copy the files to the guest for f in onlyfiles: guest_agent.copy_file(os.path.join(extracted_files_path, f), os.path.join(mwmon.files_path, f)) ex_path = str(ntpath.join(mwmon.files_path, mwmon.main_executable)) # Instruct file execution guest_agent.execute_file(ex_path) # Stop agent # guest_agent.stop_agent() # MODULE CONFIGURATION mwmon.api_tracer = config_run['modules']['api_tracer'] mwmon.interproc = config_run['modules']['interproc'] mwmon.coverage = config_run['modules']['coverage'] mwmon.dumper = config_run['modules']['dumper'] # API TRACER CONFIGURATION if mwmon.api_tracer and "api_tracer" in config_run: # Static config mwmon.api_tracer_text_log_name = config.get('api_tracer', 'text_log_name') mwmon.api_tracer_bin_log_name = config.get('api_tracer', 'bin_log_name') # Run config mwmon.api_tracer_light_mode = config_run['api_tracer']['light_mode'] mwmon.api_tracer_text_log = config_run['api_tracer']['text_log'] mwmon.api_tracer_bin_log = config_run['api_tracer']['bin_log'] if "include_apis" in config_run["api_tracer"]: mwmon.include_apis = [] mwmon.include_apis_addrs = [] for api_call in config_run["api_tracer"]["include_apis"]: try: mod, fun = api_call.split("!") mwmon.include_apis.append((mod.lower(), fun.lower())) except Exception: # Just pass over the malformed api names pass else: mwmon.include_apis = None if "exclude_apis" in config_run["api_tracer"]: mwmon.exclude_apis = [] mwmon.exclude_apis_addrs = [] for api_call in config_run["api_tracer"]["exclude_apis"]: try: mod, fun = api_call.split("!") mwmon.exclude_apis.append((mod.lower(), fun.lower())) except Exception: # Just pass over the malformed api names pass else: mwmon.excludeapis = None if "procs" in config_run["api_tracer"]: mwmon.api_tracer_procs = config_run["api_tracer"]["procs"] else: mwmon.api_tracer_procs = None if "exclude_modules" in config_run["api_tracer"]: mwmon.exclude_modules_addrs = [] mwmon.exclude_modules = [ s.lower() for s in config_run["api_tracer"]["exclude_modules"] ] else: mwmon.exclude_modules = None if "exclude_origin_modules" in config_run["api_tracer"]: mwmon.exclude_origin_modules_addrs = [] mwmon.exclude_origin_modules = [ s.lower() for s in config_run["api_tracer"]["exclude_origin_modules"] ] else: mwmon.exclude_origin_modules = None mwmon.exclude_origin_modules_addrs = None # interproc configuration if mwmon.interproc: # Static config mwmon.interproc_bin_log_name = config.get('interproc', 'bin_log_name') mwmon.interproc_text_log_name = config.get('interproc', 'text_log_name') mwmon.interproc_basic_stats_name = config.get('interproc', 'basic_stats_name') # Run config mwmon.interproc_bin_log = config_run['interproc']['bin_log'] mwmon.interproc_text_log = config_run['interproc']['text_log'] mwmon.interproc_basic_stats = config_run['interproc']['basic_stats'] if mwmon.interproc_text_log: mwmon.interproc_text_log_handle = open( mwmon.interproc_text_log_name, "w") if mwmon.coverage: # Static config mwmon.coverage_log_name = config.get('coverage', 'cov_log_name') mwmon.coverage_text_name = config.get('coverage', 'cov_text_name') # Run config if "procs" in config_run["coverage"]: mwmon.coverage_procs = config_run["coverage"]["procs"] else: mwmon.coverage_procs = None # Static config mwmon.dumper_path = config.get('dumper', 'path') # DUMPER CONFIGURATION if mwmon.dumper: if os.path.isdir(mwmon.dumper_path): shutil.rmtree(mwmon.dumper_path) os.makedirs(mwmon.dumper_path) # Run config mwmon.dumper_onexit = config_run['dumper']['dump_on_exit'] # Possible formats for dump_at: # 0x00400000 # user32.dll!CharNextW # user32.dll!CharNextW!0x00400000 if "dump_at" in config_run["dumper"]: mwmon.dumper_dumpat = config_run['dumper']['dump_at'] mwmon.printer("Initializing callbacks") mwmon.cm = CallbackManager(module_hdl, new_style=True) # Initialize first process proc_name = mwmon.main_executable mwmon.data.procs = [Process(proc_name)] procs = api.get_process_list() match_procs = [] for proc in procs: name = proc["name"] pid = proc["pid"] pgd = proc["pgd"] if proc_name is not None and (proc_name in name or name in proc_name): match_procs.append((pid, pgd, name)) if len(match_procs) == 0: mwmon.printer( "No process matching that process name, deferring process detection" ) mwmon.printer("Initializing process creation callback") # Monitor creation of new processes, to start tracing the first one. mwmon.cm.add_callback(CallbackManager.CREATEPROC_CB, new_process, name="vmi_new_proc") elif len(match_procs) == 1: mwmon.printer( "Process found with the name specified, monitoring process...") new_process({ "pid": match_procs[0][0], "pgd": match_procs[0][1], "name": match_procs[0][2] }) else: mwmon.printer( "Too many procs matching that name, please narrow down!!") if mwmon.dumper: mwmon.printer("Adding dumper commands") # Create and activate new command (dump_mwmon) add_command("dump_mwmon", dumper.dump_command) # Add a callback on process remove, to know when # we dont have any monitored process left. mwmon.cm.add_callback(CallbackManager.REMOVEPROC_CB, remove_process, name="mwmon_vmi_remove_proc") mwmon.printer("Initialized callbacks")
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 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 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)
finally: of.close() # Dump every dll. mods = dict((mod.DllBase.v(), mod) for mod in task.get_load_modules()) # List of covered_ranges contains all the address ranges already dumped, to avoid # dumping them as vads. covered_ranges = [task.Peb.ImageBaseAddress] for mod in mods.values(): mod_base = mod.DllBase.v() mod_name = mod.BaseDllName if not task_space.is_valid_address(mod_base): mwmon.printer( "Error: DllBase is unavailable (possibly due to paging)") continue else: mwmon.printer("Dumping module %s for %x" % (mod_name, task.UniqueProcessId)) dump_file = os.path.join( path, "module.{0:x}.{1:x}.dll".format(task.UniqueProcessId, mod_base)) of = open(dump_file, 'wb') pe_file = obj.Object( "_IMAGE_DOS_HEADER", offset=mod_base, vm=task_space) covered_ranges.append(mod_base) try: for offset, code in pe_file.get_image(unsafe=True, memory=False, fix=True): of.seek(offset)