def read_parameters(cpu, num_params): ''' Reads parameters from the registers/stack ''' import api TARGET_LONG_SIZE = api.get_os_bits() / 8 if TARGET_LONG_SIZE == 4: # All the parameters are on the stack # We need to read num_params values + the return address try: buff = api.r_va(cpu.CR3, cpu.ESP, (num_params + 1) * 4) except: buff = "\x00" * len((num_params + 1) * 4) mwmon.printer( "Could not read properly the parameters in interproc.py") params = struct.unpack("<" + "I" * (1 + num_params), buff) return params elif TARGET_LONG_SIZE == 8: params_regs = [] params_stack = () # Add the return address as parameter 0 try: buff = api.r_va(cpu.CR3, cpu.RSP, 8) except: buff = "\x00" * 8 mwmon.printer( "Could not read properly the parameters in interproc.py") params_regs.append(struct.unpack("<Q", buff)[0]) if num_params <= 1: params_regs.append(cpu.RCX) if num_params <= 2: params_regs.append(cpu.RDX) if num_params <= 3: params_regs.append(cpu.R8) if num_params <= 4: params_regs.append(cpu.R9) if num_params > 4: # We need to read num_params + the return address + 0x20 # 0x20 is for the 4 slots (of 8 bytes) allocated to store # register parameters (allocated by caller, used by callee) try: buff = api.r_va(cpu.CR3, cpu.RSP, (num_params + 5) * 8) except: buff = "\x00" * ((num_params + 5) * 8) mwmon.printer( "Could not read properly the parameters in interproc.py") params_stack = struct.unpack("<" + "Q" * (5 + num_params), buff) params_stack = params_stack[5:] return (tuple(params_regs) + params_stack) else: raise Exception( "[interproc::read_return_parameter(cpu)] : Non-supported TARGET_LONG_SIZE: %d" % TARGET_LONG_SIZE)
def read_parameters(cpu, num_params): ''' Reads parameters from the registers/stack ''' import api TARGET_LONG_SIZE = api.get_os_bits() / 8 if TARGET_LONG_SIZE == 4: # All the parameters are on the stack # We need to read num_params values + the return address try: buff = api.r_va(cpu.CR3, cpu.ESP, (num_params + 1) * 4) except: buff = "\x00" * len((num_params + 1) * 4) mwmon.printer("Could not read properly the parameters in interproc.py") params = struct.unpack("<" + "I" * (1 + num_params), buff) return params elif TARGET_LONG_SIZE == 8: params_regs = [] params_stack = () # Add the return address as parameter 0 try: buff = api.r_va(cpu.CR3, cpu.RSP, 8) except: buff = "\x00" * 8 mwmon.printer("Could not read properly the parameters in interproc.py") params_regs.append(struct.unpack("<Q", buff)[0]) if num_params <= 1: params_regs.append(cpu.RCX) if num_params <= 2: params_regs.append(cpu.RDX) if num_params <= 3: params_regs.append(cpu.R8) if num_params <= 4: params_regs.append(cpu.R9) if num_params > 4: # We need to read num_params + the return address + 0x20 # 0x20 is for the 4 slots (of 8 bytes) allocated to store # register parameters (allocated by caller, used by callee) try: buff = api.r_va(cpu.CR3, cpu.RSP, (num_params + 5) * 8) except: buff = "\x00" * ((num_params + 5) * 8) mwmon.printer("Could not read properly the parameters in interproc.py") params_stack = struct.unpack( "<" + "Q" * (5 + num_params), buff) params_stack = params_stack[5:] return (tuple(params_regs) + params_stack) else: raise Exception( "[interproc::read_return_parameter(cpu)] : Non-supported TARGET_LONG_SIZE: %d" % TARGET_LONG_SIZE)
def gdb_memory_rw_debug(thread_id, thread_list, addr, length, buf, is_write): ''' Read / Write memory ''' thread = None # First, check if we can read the register from the CPU object for element in thread_list: if element['id'] == thread_id: thread = element break if thread is None: return None if is_write: from api import w_va w_va(thread['pgd'], addr, buf, length) return buf else: try: from api import r_va import binascii mem = r_va(thread['pgd'], addr, length) return mem except Exception as e: raise e
def ntmapviewofsection_ret(params, cm, callback_name, mapped_sec, mapping_proc, base_p, size_p, offset_p, proc, update_vads, long_size): from core import SectionMap import api global interproc_data global interproc_config TARGET_LONG_SIZE = api.get_os_bits() / 8 cpu_index = params["cpu_index"] cpu = params["cpu"] pgd = api.get_running_process(cpu_index) # First, remove callback cm.rm_callback(callback_name) if base_p != 0: base = dereference_target_long(base_p, pgd, long_size) else: base = 0 if size_p != 0: size = dereference_target_long(size_p, pgd, long_size) else: size = 0 # Offset is always 8 bytes if offset_p != 0: try: offset = struct.unpack("Q", api.r_va(pgd, offset_p, 8))[0] except: offset = 0 pp_debug( "Could not dereference offset in NtMapViewOfSection return, in interproc_callbacks.py\n" ) else: offset = 0 mapping_proc.add_section_map( SectionMap(mapped_sec, pgd, base, size, offset)) if interproc_config.interproc_text_log and interproc_config.interproc_text_log_handle is not None: f = interproc_config.interproc_text_log_handle if TARGET_LONG_SIZE == 4: f.write( "[PID: %08x] NtMapViewOfSection: Base: %08x Size: %08x Offset: %08x / Section: %s\n" % (proc.get_pid(), base, size, offset, mapped_sec.get_backing_file())) elif TARGET_LONG_SIZE == 8: f.write( "[PID: %08x] NtMapViewOfSection: Base: %16x Size: %16x Offset: %08x / Section: %s\n" % (proc.get_pid(), base, size, offset, mapped_sec.get_backing_file())) if update_vads: proc.update_vads()
def read(pgd, addr, length): ''' Wrapper to read data from memory ''' import api try: return api.r_va(pgd, addr, length) except: return "\x00" * length
def ntmapviewofsection_ret(params, pid, callback_name, mapped_sec, mapping_proc, base_p, size_p, offset_p, proc, update_vads): from mw_monitor_classes import mwmon from mw_monitor_classes import SectionMap import api TARGET_LONG_SIZE = api.get_os_bits() / 8 cpu_index = params["cpu_index"] cpu = params["cpu"] pgd = api.get_running_process(cpu_index) # First, remove callback mwmon.cm.rm_callback(callback_name) if base_p != 0: base = dereference_target_long(base_p, pgd) else: base = 0 if size_p != 0: size = dereference_target_long(size_p, pgd) else: size = 0 # Offset is always 8 bytes if offset_p != 0: try: offset = struct.unpack("Q", api.r_va(pgd, offset_p, 8))[0] except: offset = 0 mwmon.printer("Could not dereference offset in NtMapViewOfSection return, in interproc.py") else: offset = 0 mapping_proc.section_maps.append( SectionMap(mapped_sec, base, size, offset)) if mwmon.interproc_text_log and mwmon.interproc_text_log_handle is not None: f = mwmon.interproc_text_log_handle if TARGET_LONG_SIZE == 4: f.write("[PID: %x] NtMapViewOfSection: Base: %08x Size: %08x Offset: %08x / Section: %s\n" % (pid, base, size, offset, mapped_sec.backing_file)) elif TARGET_LONG_SIZE == 8: f.write("[PID: %x] NtMapViewOfSection: Base: %16x Size: %16x Offset: %08x / Section: %s\n" % (pid, base, size, offset, mapped_sec.backing_file)) if update_vads: proc.update_vads()
def dereference_target_long(addr, pgd): import api TARGET_LONG_SIZE = api.get_os_bits() / 8 typ = "<I" if TARGET_LONG_SIZE == 4 else "<Q" try: buff = api.r_va(pgd, addr, TARGET_LONG_SIZE) except: buff = "\x00" * TARGET_LONG_SIZE mwmon.printer("Could not dereference TARGET_LONG in interproc.py") return struct.unpack(typ, buff)[0]
def __opcode_range_callback(self, params): """ Called by the callback manager when the desired opcode is hit. """ cpu_index = params["cpu_index"] cpu = params["cpu"] cur_pc = params["cur_pc"] next_pc = params["next_pc"] try: if self.__status == GuestAgentPlugin.__AGENT_READY: function = api.r_va(api.get_running_process(cpu_index), cur_pc + 3, 2) try: handler = { "\x00\x00": self.__handle_host_version, "\x00\x01": self.__handle_host_message, "\x00\x02": self.__handle_host_get_command, "\x10\x00": self.__handle_host_open, "\x10\x01": self.__handle_host_read, "\x10\x02": self.__handle_host_close, "\x10\x03": self.__handle_host_get_file_name, "\x20\x00": self.__handle_host_request_exec_path, "\x20\x01": self.__handle_host_request_exec_args, "\x20\x02": self.__handle_host_request_exec_env, "\x20\x03": self.__handle_host_request_exec_args_linux, "\x20\x04": self.__handle_host_request_exec_env_linux }[function] handler(cpu_index, cpu) except KeyError: self.__printer( "HostFilePlugin: Unknown host opcode %x at 0x%08x" % (function, cur_pc)) # Advance the program counter. # Needs to be done explicitly, as Qemu doesn't know the instruction # length. if isinstance(cpu, X86CPU): api.w_r(cpu_index, "EIP", cpu.EIP + 10) elif isinstance(cpu, X64CPU): api.w_r(cpu_index, "RIP", cpu.RIP + 10) elif self.__status == GuestAgentPlugin.__AGENT_RUNNING: # Agent already running but not ready yet (the base # address was not correctly determined yet. # Advance the program counter. # Needs to be done explicitly, as Qemu doesn't know the instruction # length. if isinstance(cpu, X86CPU): api.w_r(cpu_index, "EIP", cpu.EIP + 10) elif isinstance(cpu, X64CPU): api.w_r(cpu_index, "RIP", cpu.RIP + 10) except Exception as e: self.__printer("Exception occurred on opcode callback: %s" % str(e))
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 __read_string(self, cpu_index, addr): """ Read a string from the virtual address space of the guest's user mode. """ pgd = api.get_running_process(cpu_index) string = [] for i in range(256): byte = api.r_va(pgd, addr + i, 1) if byte == "\x00": break string.append(byte) return "".join(string)
def dereference_target_long(addr, pgd, long_size): import api typ = "<I" if long_size == 4 else "<Q" try: buff = api.r_va(pgd, addr, long_size) except: buff = "\x00" * long_size pp_debug("Could not dereference TARGET_LONG in interproc_callbacks.py") if len(buff) == 0: pp_warning( "[interproc_callbacks.py:dereference_target_long] Error while dereferencing parameter with address %x" % addr) return 0 return struct.unpack(typ, buff)[0]
def load_module(params): ''' Callback trigger for every module loaded. ''' global cm global pyrebox_print global entry_point_bp global target_pgd global target_procname import pefile import api from api import BP pid = params["pid"] pgd = params["pgd"] base = params["base"] size = params["size"] name = params["name"] fullname = params["fullname"] if pgd == target_pgd and target_procname.lower().startswith(name.lower()): # Loaded main module, try to read EP ep = None try: pe_data = api.r_va(pgd, base, 0x1000) pe = pefile.PE(data=pe_data) ep = base + pe.OPTIONAL_HEADER.AddressOfEntryPoint except Exception as e: print(e) pyrebox_print("Could not read EP from module %s on load" % name) # If we have the EP, put a breakpoint there if ep is not None: pyrebox_print("The entry point for %s is 0x%x\n" % (target_procname, ep)) cm.rm_callback("load_module") pyrebox_print("Setting BP on entrypoint") # Set a breakpoint on the EP, that will start a shell entry_point_bp = BP(ep, pgd, new_style=True, func=module_entry_point) entry_point_bp.enable()
def find_ep(pgd, proc_name): '''Given an address space and a process name, uses pefile module to get its entry point ''' global cm global loaded_processes import api for m in api.get_module_list(pgd): name = m["name"] base = m["base"] # size = m["size"] if name == proc_name: try: pe_data = api.r_va(pgd, base, 0x1000) pe = pefile.PE(data=pe_data) ep = pe.OPTIONAL_HEADER.AddressOfEntryPoint return (base + ep) except Exception: pyrebox_print("Unable to run pefile on loaded module %s" % name)
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 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 opcodes(params, cb_name, proc): from api import CallbackManager import api from deviare_db_parser import ArgumentParser import struct global bp_counter global APITRACER_DATABASE32 global APITRACER_DATABASE64 global APITRACER_RULES global APITRACER_ANTI_STOLEN 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"] pgd = api.get_running_process(cpu_index) try: if APITRACER_ANTI_STOLEN: # Locate nearest lower symbol sym = proc.locate_nearest_symbol(next_pc) else: sym = proc.locate_nearest_symbol(next_pc, tolerate_offset=0x0) if sym is None: return mod = sym.get_mod() mod_fullname = sym.get_mod_fullname() fun = sym.get_fun() real_api_addr = sym.get_addr() caller_module_name = proc.get_overlapping_module(pc) # Reduce FP's by checking that the origin EIP is not also within the # same module (code reuse inside the dll) if mod_fullname == caller_module_name: return matched = False # Check if the API is in the list (included or excluded) for rule in APITRACER_RULES["rules"]: if rule["mod"] == "" or fnmatch.fnmatch(mod_fullname.lower(), rule["mod"].lower()): if rule["fun"] == "" or fnmatch.fnmatch( fun.lower(), rule["fun"].lower()): if "from_mod" in rule and rule["from_mod"] != "": if caller_module_name is not None: if fnmatch.fnmatch(caller_module_name, rule["from_mod"].lower()): matched = True if rule["action"] == "reject": return else: break break else: matched = True if rule["action"] == "reject": return else: break # Apply default policy if not matched: if not matched and APITRACER_RULES["policy"] == "reject": return # 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 Exception as e: ret_addr = 0 pyrebox_print( "Could not read return address on API tracer: %s" % str(e)) 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 Exception as e: ret_addr = 0 pyrebox_print( "Could not read return address on API tracer: %s" % str(e)) is_64_bit_dll = True if TARGET_LONG_SIZE == 4 or (TARGET_LONG_SIZE == 8 and proc.is_wow64() and "windows\\syswow64" in mod_fullname.lower()): is_64_bit_dll = False if APITRACER_LIGHT_MODE: bits = 32 if not is_64_bit_dll else 64 if real_api_addr == next_pc: if TARGET_LONG_SIZE == 4: proc.add_call( pc, real_api_addr, "[PID: %x] (%d) %08x --> %s:%s(%08x) --> %08x\n" % (proc.get_pid(), bits, pc, mod_fullname, fun, real_api_addr, ret_addr)) elif TARGET_LONG_SIZE == 8: proc.add_call( pc, real_api_addr, "[PID: %x] (%d) %016x --> %s:%s(%016x) --> %016x\n" % (proc.get_pid(), bits, pc, mod_fullname, fun, real_api_addr, ret_addr)) else: if TARGET_LONG_SIZE == 4: proc.add_call( pc, real_api_addr, "[PID: %x] (%d) %08x --> %s:%s(+%x)(%08x) --> %08x\n" % (proc.get_pid(), bits, pc, mod_fullname, fun, (next_pc - real_api_addr), next_pc, ret_addr)) elif TARGET_LONG_SIZE == 8: proc.add_call( pc, real_api_addr, "[PID: %x] (%d) %016x --> %s:%s(+%x)(%016x) --> %016x\n" % (proc.get_pid(), bits, pc, mod_fullname, fun, (next_pc - real_api_addr), next_pc, ret_addr)) return data = APICallData() data.set_pc(pc) data.set_mod(mod) data.set_fun(fun) data.set_ret_addr(ret_addr) if not is_64_bit_dll: argument_parser = ArgumentParser(cpu, cpu.ESP, mod, fun, 32) else: argument_parser = ArgumentParser(cpu, cpu.RSP, mod, fun, 64) if not argument_parser.in_db(): #pyrebox_print("API function not present in db: %s - %s" % (mod, fun)) return data.set_in_args([arg for arg in argument_parser.get_in_args()]) # Add the call as soon as it is produced, and update # the output parameters on return proc.add_call(pc, real_api_addr, data) # If return address could not be read, we skip the callback if ret_addr != 0: callback_name = "ret_bp_%d" % bp_counter callback = functools.partial(opcodes_ret, pc, real_api_addr, data, callback_name, argument_parser, mod, fun, proc) cm.add_callback(CallbackManager.INSN_BEGIN_CB, callback, name=callback_name, addr=data.get_ret_addr(), pgd=pgd) bp_counter += 1 except Exception as e: pyrebox_print(str(e)) traceback.print_exc() finally: return
def linux_insert_module(task, pid, pgd, base, size, basename, fullname, update_symbols=False): from utils import ConfigurationManager as conf_m import volatility.obj as obj from vmi import modules from vmi import symbols from vmi import Module import api import hashlib pgd_for_memory_read = conf_m.addr_space.vtop(task.mm.pgd) or task.mm.pgd # Create module, use 0 as checksum as it is irrelevant here mod = Module(base, size, pid, pgd, 0, basename, fullname) # Add an entry in the module list, if necessary if (pid, pgd) not in modules: modules[(pid, pgd)] = {} # Add the module to the module list if base in modules[(pid, pgd)]: del modules[(pid, pgd)][base] modules[(pid, pgd)][base] = mod if update_symbols: # Compute the checksum of the ELF Header, as a way to avoid name # collisions on the symbol cache. May extend this hash to other parts # of the binary if necessary in the future. elf_hdr = obj.Object( "elf_hdr", offset=base, vm=task.get_process_address_space()) if elf_hdr.is_valid(): elf_hdr_size = elf_hdr.elf_obj.size() buf = api.r_va(pgd_for_memory_read, base, elf_hdr_size) h = hashlib.sha256() h.update(buf) checksum = h.hexdigest() if (checksum, fullname) not in symbols: symbols[(checksum, fullname)] = {} syms = symbols[(checksum, fullname)] # Fetch symbols for sym in elf_hdr.symbols(): if sym.st_value == 0 or (sym.st_info & 0xf) != 2: continue sym_name = elf_hdr.symbol_name(sym) sym_offset = sym.st_value if sym_name in syms: if syms[sym_name] != sym_offset: # There are cases in which the same import is present twice, such as in this case: # nm /lib/x86_64-linux-gnu/libpthread-2.24.so | grep "pthread_getaffinity_np" # 00000000000113f0 T pthread_getaffinity_np@GLIBC_2.3.3 # 00000000000113a0 T # pthread_getaffinity_np@@GLIBC_2.3.4 sym_name = sym_name + "_" while sym_name in syms and syms[sym_name] != sym_offset: sym_name = sym_name + "_" if sym_name not in syms: syms[sym_name] = sym_offset else: syms[sym_name] = sym_offset mod.set_symbols(symbols[(checksum, fullname)]) return None
def ntreadfile(params, pid, proc, update_vads, is_write=False): import volatility.win32.tasks as tasks from mw_monitor_classes import mwmon from mw_monitor_classes import FileRead from mw_monitor_classes import FileWrite from mw_monitor_classes import File from utils import get_addr_space import api TARGET_LONG_SIZE = api.get_os_bits() / 8 cpu_index = params["cpu_index"] cpu = params["cpu"] # IN HANDLE FileHandle, # IN HANDLE Event OPTIONAL, # IN PIO_APC_ROUTINE ApcRoutine OPTIONAL, # IN PVOID ApcContext OPTIONAL, # OUT PIO_STATUS_BLOCK IoStatusBlock, # OUT PVOID Buffer, # IN ULONG Length, # IN PLARGE_INTEGER ByteOffset OPTIONAL, # IN PULONG Key OPTIONAL pgd = api.get_running_process(cpu_index) # Read the parameters ret_addr, file_handle, arg2, arg3, arg4, arg5, buff, length, offset_p, arg9 = read_parameters( cpu, 9) # Load volatility address space addr_space = get_addr_space(pgd) # Get list of processes, and filter out by the process that triggered the # call (current process id) eprocs = [t for t in tasks.pslist(addr_space) if t.UniqueProcessId == pid] # Initialize file_obj, that will point to the object of the referenced file file_obj = None # Search handle table for the new created process for task in eprocs: if task.UniqueProcessId == pid and task.ObjectTable.HandleTableList: for handle in task.ObjectTable.handles(): if handle.is_valid() and handle.HandleValue == file_handle and handle.get_object_type() == "File": file_obj = handle.dereference_as("_FILE_OBJECT") break break if file_obj is not None: file_instance = None for fi in mwmon.data.files: if fi.file_name == str(file_obj.FileName): file_instance = fi break # If we have still not recorded the file, add it to files to record if file_instance is None: file_instance = File(str(file_obj.FileName)) mwmon.data.files.append(file_instance) # Now, record the read/write # curr_file_offset is never used # curr_file_offset = int(file_obj.CurrentByteOffset.QuadPart) # FO_SYNCHRONOUS_IO 0x0000002 is_offset_maintained = ((file_obj.Flags & 0x0000002) != 0) # If no offset was specified, and the offset is mantained, the real # offset is taken from the file object offset = None if offset_p == 0 and is_offset_maintained: offset = int(file_obj.CurrentByteOffset.QuadPart) elif offset_p != 0: # If an offset is provided, the current offset in the file_object # will be updated, regardless of the flag. try: offset = struct.unpack("Q", api.r_va(pgd, offset_p, 8))[0] except: offset = 0 mwmon.printer("Could not dereference offset in NtReadFile call in interproc.py") else: # If no offset was specified and the file object does not have the flag set, we may be in front of some kind # of corruption error or deliberate manipulation print "[!] The file object flag FO_SYNCHRONOUS_IO is not set, and no offset was provided" return # At this moment we do not record the data op = None for proc in mwmon.data.procs: if proc.pid == pid: local_proc = proc break if not is_write: op = FileRead(file_instance, local_proc, offset, length, None) if mwmon.interproc_text_log and mwmon.interproc_text_log_handle is not None: f = mwmon.interproc_text_log_handle if TARGET_LONG_SIZE == 4: f.write("[PID: %x] NtReadFile: Offset: %08x Size: %08x / %s\n" % (pid, offset, length, str(file_obj.FileName))) elif TARGET_LONG_SIZE == 8: f.write("[PID: %x] NtReadFile: Offset: %16x Size: %16x / %s\n" % (pid, offset, length, str(file_obj.FileName))) else: op = FileWrite(file_instance, local_proc, offset, length, None) if mwmon.interproc_text_log and mwmon.interproc_text_log_handle is not None: f = mwmon.interproc_text_log_handle if TARGET_LONG_SIZE == 4: f.write("[PID: %x] NtWriteFile: Offset: %08x Size: %08x / %s\n" % (pid, offset, length, str(file_obj.FileName))) elif TARGET_LONG_SIZE == 8: f.write("[PID: %x] NtWriteFile: Offset: %16x Size: %16x / %s\n" % (pid, offset, length, str(file_obj.FileName))) file_instance.add_operation(op) local_proc.file_operations.append(op) if update_vads: proc.update_vads()
def 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 windows_read_paged_out_memory(pgd, addr, size): import api import api_internal import struct from utils import get_addr_space VALID_BIT = 0x1 PROTOTYPE_BIT = 0x1 << 10 TRANSITION_BIT = 0x1 << 11 PPTE_VALID_BIT = 0x1 PPTE_TRANSITION_BIT = 0x1 << 11 PPTE_DIRTY_BIT = 0x1 << 6 PPTE_P_BIT = 0x1 << 10 # Thit bit means it is a... #...memory mapped file, instead of "prototype" # Get PTE and 'mode' pte = api_internal.x86_get_pte(pgd, addr) is_pae = api_internal.x86_is_pae() bitness = api.get_os_bits() addr_space = get_addr_space(pgd) # if PTE is None, it could mean that the page directory has not been created. # if PTE is 0, it could mean the pte has not been yet created. # In these cases, we still check the VAD to see if it corresponds to a # memory mapped file, and if so, read that file at the correct offset if pte is None or pte == 0: return windows_read_memory_mapped(pgd, addr, size, pte, is_pae, bitness) # Make sure the page is invalid and we cannot read it: if (pte & VALID_BIT) == 1: return api.r_va(pgd, addr, size) # The PTE is INVALID. First, we check it doesn't point to a # prototype page table entry (PPTE). if (pte & PROTOTYPE_BIT) == 0: # The page is in the pagefile, or is demand zero, or memory mapped if (pte & TRANSITION_BIT) == 0: number_bits_offset = 0 number_bits_number = 0 page_file_offset = 0 page_file_number = 0 if (bitness == 32 and not is_pae) or bitness == 64: offset_mask = generate_mask( addr_space.profile.vtypes["_MMPTE_SOFTWARE"][1] ["PageFileHigh"][1][1]["start_bit"], addr_space.profile.vtypes["_MMPTE_SOFTWARE"][1] ["PageFileHigh"][1][1]["end_bit"]) number_mask = generate_mask( addr_space.profile.vtypes["_MMPTE_SOFTWARE"][1] ["PageFileLow"][1][1]["start_bit"], addr_space.profile.vtypes["_MMPTE_SOFTWARE"][1] ["PageFileLow"][1][1]["end_bit"]) number_bits_offset = addr_space.profile.vtypes["_MMPTE_SOFTWARE"][1]["PageFileHigh"][1][1]["end_bit"] - \ addr_space.profile.vtypes["_MMPTE_SOFTWARE"][1]["PageFileHigh"][1][1]["start_bit"] number_bits_number = addr_space.profile.vtypes["_MMPTE_SOFTWARE"][1]["PageFileLow"][1][1]["end_bit"] - \ addr_space.profile.vtypes["_MMPTE_SOFTWARE"][1]["PageFileLow"][1][1]["start_bit"] page_file_offset = ( pte & offset_mask ) >> addr_space.profile.vtypes["_MMPTE_SOFTWARE"][1][ "PageFileHigh"][1][1]["start_bit"] page_file_number = ( pte & number_mask) >> addr_space.profile.vtypes[ "_MMPTE_SOFTWARE"][1]["PageFileLow"][1][1]["start_bit"] elif bitness == 32 and is_pae: # See Intel manual, consider 24 bits of address for the 4KiB page offset # Page file offset should correspond to the same 24 bits offset_mask = generate_mask(12, 12 + 24) # Reuse the same as for 32/64 bits number_mask = generate_mask( addr_space.profile.vtypes["_MMPTE_SOFTWARE"][1] ["PageFileLow"][1][1]["start_bit"], addr_space.profile.vtypes["_MMPTE_SOFTWARE"][1] ["PageFileLow"][1][1]["end_bit"]) number_bits_offset = 24 number_bits_number = addr_space.profile.vtypes["_MMPTE_SOFTWARE"][1]["PageFileLow"][1][1]["end_bit"] - \ addr_space.profile.vtypes["_MMPTE_SOFTWARE"][1]["PageFileLow"][1][1]["start_bit"] page_file_offset = (pte & offset_mask) >> 12 page_file_number = ( pte & number_mask) >> addr_space.profile.vtypes[ "_MMPTE_SOFTWARE"][1]["PageFileLow"][1][1]["start_bit"] else: raise NotImplementedError() #Demand zero if page_file_offset == 0x0: return "\x00" * size #Check VAD (memory mapped file) (all 1's) elif page_file_offset == generate_mask(0, number_bits_offset): return windows_read_memory_mapped(pgd, addr, size, pte, is_pae, bitness) # Page file else: return windows_read_paged_file(pgd, addr, size, page_file_offset, page_file_number) # Transition page -> Can be read normally from memory, # so proceed with the read even if valid bit is 0. else: # Get the offset from the PTE, and compute ourselves the physical address page_offset = 0 if (bitness == 32 and not is_pae) or bitness == 64: offset_mask = generate_mask( addr_space.profile.vtypes["_MMPTE_HARDWARE"][1] ["PageFrameNumber"][1][1]["start_bit"], addr_space.profile.vtypes["_MMPTE_HARDWARE"][1] ["PageFrameNumber"][1][1]["end_bit"]) page_offset = (pte & offset_mask) elif bitness == 32 and is_pae: # See Intel manual, consider 24 bits of address for the 4KiB page offset # Page file offset should correspond to the same 24 bits offset_mask = generate_mask(12, 12 + 24) # Reuse the same as for 32/64 bits page_offset = (pte & offset_mask) else: raise NotImplementedError() # Read physical address, always 12 bits for a 4KiB page. # XXX: Here, we should should also consider 4Mb pages. return api.r_pa(page_offset | (addr & generate_mask(0, 12)), size) # The page points to a prototype PTE else: # We read the PPTE ppte_addr = 0 ppte_size = 0 if bitness == 32 and not is_pae: # In this case, the PPTE pointer is not a pointer, but an index, so it # needs some additional computation index_low_mask = generate_mask( addr_space.profile.vtypes["_MMPTE_PROTOTYPE"][1] ["ProtoAddressLow"][1][1]["start_bit"], addr_space.profile.vtypes["_MMPTE_PROTOTYPE"][1] ["ProtoAddressLow"][1][1]["end_bit"]) index_low = (pte & index_low_mask) >> addr_space.profile.vtypes[ "_MMPTE_PROTOTYPE"][1]["ProtoAddressLow"][1][1]["start_bit"] index_high_mask = generate_mask( addr_space.profile.vtypes["_MMPTE_PROTOTYPE"][1] ["ProtoAddressHigh"][1][1]["start_bit"], addr_space.profile.vtypes["_MMPTE_PROTOTYPE"][1] ["ProtoAddressHigh"][1][1]["end_bit"]) index_high = (pte & index_high_mask) >> addr_space.profile.vtypes[ "_MMPTE_PROTOTYPE"][1]["ProtoAddressHigh"][1][1]["start_bit"] number_bits_index_low = addr_space.profile.vtypes["_MMPTE_PROTOTYPE"][1]["ProtoAddressLow"][1][1]["end_bit"]- \ addr_space.profile.vtypes["_MMPTE_PROTOTYPE"][1]["ProtoAddressLow"][1][1]["start_bit"] ppte_size = addr_space.profile.vtypes["_MMPTE"][0] # Formula to compute the index index = ((index_high << number_bits_index_low) | index_low) << 2 # The index is an address relative to the base of a paged pool. # By debugging several systems, these bases where fixed (on 32 bit systems) # to 0x80000000 or 0xe1000000 (windows 7 and windows xp respectively). # This address points to one of the PrototypePTEs that are part of the Segment, # pointed out by the ControlArea of the corresponding VAD. Therefore, we should # be able to bruteforce the first 8 bits of the address to find the correct base. # First, get the Segment, and the first prototype PTE, as well as the number of # prototype PTEs for that Segment. res = windows_get_prototype_pte_address_range(pgd, addr) found = False if res is not None: start, end = res for i in range(0, 255): ppte_addr = (i << 24) | index if ppte_addr >= start and ppte_addr <= end: found = True break if not found: raise RuntimeError( "Could not read memory on second chance (using filesystem)" ) else: raise RuntimeError( "Could not read memory on second chance (using filesystem)" ) elif bitness == 32 and is_pae: # According to paper: "Windows Operating Systems Agnostic Memory Analysis" offset_mask = generate_mask(32, 64) ppte_addr = (pte & offset_mask) >> 32 ppte_size = 64 elif bitness == 64: offset_mask = generate_mask( addr_space.profile.vtypes["_MMPTE_PROTOTYPE"][1] ["ProtoAddress"][1][1]["start_bit"], addr_space.profile.vtypes["_MMPTE_PROTOTYPE"][1] ["ProtoAddress"][1][1]["end_bit"]) ppte_addr = (pte & offset_mask) >> addr_space.profile.vtypes[ "_MMPTE_PROTOTYPE"][1]["ProtoAddress"][1][1]["start_bit"] ppte_size = addr_space.profile.vtypes["_MMPTE"][0] else: raise NotImplementedError() # Now, read the PPTE given its address. The PPTE address is a virtual address!!!! (on a paged pool) if ppte_size == 4: ppte = struct.unpack("<I", api.r_va(pgd, ppte_addr, 4))[0] elif ppte_size == 8: ppte = struct.unpack("<K", api.r_va(pgd, ppte_addr, 8))[0] else: raise NotImplementedError() if (ppte & PPTE_VALID_BIT == 1) or (ppte & PPTE_TRANSITION_BIT == 1): # State: Active/Valid - Transision - Modified-no-write # The PPTE contains a valid entry, so we can # just use the info in it to translate the page. # Get the offset from the PTE, and compute ourselves the physical address page_offset = 0 if (bitness == 32 and not is_pae) or bitness == 64: offset_mask = generate_mask( addr_space.profile.vtypes["_MMPTE_HARDWARE"][1] ["PageFrameNumber"][1][1]["start_bit"], addr_space.profile.vtypes["_MMPTE_HARDWARE"][1] ["PageFrameNumber"][1][1]["end_bit"]) page_offset = (ppte & offset_mask) elif bitness == 32 and is_pae: # See Intel manual, consider 24 bits of address for the 4KiB page offset # Page file offset should correspond to the same 24 bits offset_mask = generate_mask(12, 12 + 24) # Reuse the same as for 32/64 bits page_offset = (ppte & offset_mask) else: raise NotImplementedError() # Read physical address, always 12 bits for a 4KiB page. # XXX: Here, we should should also consider 4Mb pages. return api.r_pa(page_offset | (addr & generate_mask(0, 12)), size) elif (ppte & (PPTE_VALID_BIT | PPTE_TRANSITION_BIT | PPTE_P_BIT)) == 0: # Demand zero or pagefile #Read page_file_offset and page_file_number page_file_offset = 0 page_file_number = 0 if (bitness == 32 and not is_pae) or bitness == 64: offset_mask = generate_mask( addr_space.profile.vtypes["_MMPTE_SOFTWARE"][1] ["PageFileHigh"][1][1]["start_bit"], addr_space.profile.vtypes["_MMPTE_SOFTWARE"][1] ["PageFileHigh"][1][1]["end_bit"]) number_mask = generate_mask( addr_space.profile.vtypes["_MMPTE_SOFTWARE"][1] ["PageFileLow"][1][1]["start_bit"], addr_space.profile.vtypes["_MMPTE_SOFTWARE"][1] ["PageFileLow"][1][1]["end_bit"]) page_file_offset = ( pte & offset_mask ) >> addr_space.profile.vtypes["_MMPTE_SOFTWARE"][1][ "PageFileHigh"][1][1]["start_bit"] page_file_number = ( pte & number_mask) >> addr_space.profile.vtypes[ "_MMPTE_SOFTWARE"][1]["PageFileLow"][1][1]["start_bit"] elif bitness == 32 and is_pae: # See Intel manual, consider 24 bits of address for the 4KiB page offset # Page file offset should correspond to the same 24 bits offset_mask = generate_mask(12, 12 + 24) # Reuse the same as for 32/64 bits number_mask = generate_mask( addr_space.profile.vtypes["_MMPTE_SOFTWARE"][1] ["PageFileLow"][1][1]["start_bit"], addr_space.profile.vtypes["_MMPTE_SOFTWARE"][1] ["PageFileLow"][1][1]["end_bit"]) page_file_offset = (pte & offset_mask) >> 12 page_file_number = ( pte & number_mask) >> addr_space.profile.vtypes[ "_MMPTE_SOFTWARE"][1]["PageFileLow"][1][1]["start_bit"] else: raise NotImplementedError() if page_file_offset == 0 and page_file_number == 0: #Demand zero return "\x00" * size else: # PageFile return windows_read_paged_file(pgd, addr, size, page_file_offset, page_file_number) elif (ppte & PPTE_P_BIT) == 1: # Memory mapped file return windows_read_memory_mapped(pgd, addr, size, ppte, is_pae, bitness)
def linux_insert_module(task, pid, pgd, base, size, basename, fullname, update_symbols=False): from utils import ConfigurationManager as conf_m import volatility.obj as obj from vmi import modules from vmi import symbols from vmi import Module from api_internal import dispatch_module_load_callback from api_internal import dispatch_module_remove_callback import api import hashlib pgd_for_memory_read = conf_m.addr_space.vtop(task.mm.pgd) or task.mm.pgd # Create module, use 0 as checksum as it is irrelevant here mod = Module(base, size, pid, pgd, 0, basename, fullname) # Add an entry in the module list, if necessary if (pid, pgd) not in modules: modules[(pid, pgd)] = {} #Module load/del notification if base in modules[(pid, pgd)]: if modules[(pid, pgd)][base].get_size() != size or \ modules[(pid, pgd)][base].get_checksum() != checksum or \ modules[(pid, pgd)][base].get_name() != basename or \ modules[(pid, pgd)][base].get_fullname() != fullname: # Notify of module deletion and module load dispatch_module_remove_callback(pid, pgd, base, modules[(pid, pgd)][base].get_size(), modules[(pid, pgd)][base].get_name(), modules[(pid, pgd)][base].get_fullname()) del modules[(pid, pgd)][base] dispatch_module_load_callback(pid, pgd, base, size, basename, fullname) modules[(pid, pgd)][base] = mod else: # Just notify of module load dispatch_module_load_callback(pid, pgd, base, size, basename, fullname) modules[(pid, pgd)][base] = mod # Mark the module as present modules[(pid, pgd)][base].set_present() if update_symbols: # Compute the checksum of the ELF Header, as a way to avoid name # collisions on the symbol cache. May extend this hash to other parts # of the binary if necessary in the future. elf_hdr = obj.Object( "elf_hdr", offset=base, vm=task.get_process_address_space()) if elf_hdr.is_valid(): elf_hdr_size = elf_hdr.elf_obj.size() buf = "" try: buf = api.r_va(pgd_for_memory_read, base, elf_hdr_size) except: pp_warning("Could not read ELF header at address %x" % base) h = hashlib.sha256() h.update(buf) checksum = h.hexdigest() if (checksum, fullname) not in symbols: symbols[(checksum, fullname)] = {} syms = symbols[(checksum, fullname)] # Fetch symbols for sym in elf_hdr.symbols(): if sym.st_value == 0 or (sym.st_info & 0xf) != 2: continue sym_name = elf_hdr.symbol_name(sym) sym_offset = sym.st_value if sym_name in syms: if syms[sym_name] != sym_offset: # There are cases in which the same import is present twice, such as in this case: # nm /lib/x86_64-linux-gnu/libpthread-2.24.so | grep "pthread_getaffinity_np" # 00000000000113f0 T pthread_getaffinity_np@GLIBC_2.3.3 # 00000000000113a0 T # pthread_getaffinity_np@@GLIBC_2.3.4 sym_name = sym_name + "_" while sym_name in syms and syms[sym_name] != sym_offset: sym_name = sym_name + "_" if sym_name not in syms: syms[sym_name] = sym_offset else: syms[sym_name] = sym_offset mod.set_symbols(symbols[(checksum, fullname)]) return None
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 __init__(self, pgd, section_object): import api global interproc_data 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 pp_print("Could not read flags in Section __init__\n") 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 "_EX_FAST_REF" in str(type(file_obj)): # 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))) self.__backing_file = interproc_data.get_file_by_file_name( str(file_obj.FileName)) # 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)) interproc_data.add_file(self.__backing_file) self.__unpickled = False self.__offset = self.__section_object.obj_offset
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 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 linux_insert_module(task, pid, pgd, base, size, basename, fullname, update_symbols=False): from utils import ConfigurationManager as conf_m import volatility.obj as obj from vmi import add_symbols from vmi import get_symbols from vmi import has_symbols from vmi import add_module from vmi import has_module from vmi import get_module from vmi import Module from api_internal import dispatch_module_load_callback from api_internal import dispatch_module_remove_callback import api import hashlib pgd_for_memory_read = conf_m.addr_space.vtop(task.mm.pgd) or task.mm.pgd # Create module, use 0 as checksum as it is irrelevant here mod = Module(base, size, pid, pgd, 0, basename, fullname) #Module load/del notification if has_module(pid, pgd, base): ex_mod = get_module(pid, pgd, base) if ex_mod.get_size() != size or \ ex_mod.get_checksum() != checksum or \ ex_mod.get_name() != basename or \ ex_mod.get_fullname() != fullname: # Notify of module deletion and module load dispatch_module_remove_callback(pid, pgd, base, ex_mod.get_size(), ex_mod.get_name(), ex_mod.get_fullname()) add_module(pid, pgd, base, mod) dispatch_module_load_callback(pid, pgd, base, size, basename, fullname) else: # Just notify of module load dispatch_module_load_callback(pid, pgd, base, size, basename, fullname) add_module(pid, pgd, base, mod) # Mark the module as present get_module(pid, pgd, base).set_present() if update_symbols: # Compute the checksum of the ELF Header, as a way to avoid name # collisions on the symbol cache. May extend this hash to other parts # of the binary if necessary in the future. elf_hdr = obj.Object("elf_hdr", offset=base, vm=task.get_process_address_space()) if elf_hdr.is_valid(): elf_hdr_size = elf_hdr.elf_obj.size() buf = "" try: buf = api.r_va(pgd_for_memory_read, base, elf_hdr_size) except: pp_warning("Could not read ELF header at address %x" % base) if not has_symbols(fullname): syms = {} # Fetch symbols for sym in elf_hdr.symbols(): if sym.st_value == 0 or (sym.st_info & 0xf) != 2: continue sym_name = elf_hdr.symbol_name(sym) sym_offset = sym.st_value if sym_name in syms: if syms[sym_name] != sym_offset: # There are cases in which the same import is present twice, such as in this case: # nm /lib/x86_64-linux-gnu/libpthread-2.24.so | grep "pthread_getaffinity_np" # 00000000000113f0 T pthread_getaffinity_np@GLIBC_2.3.3 # 00000000000113a0 T # pthread_getaffinity_np@@GLIBC_2.3.4 sym_name = sym_name + "_" while sym_name in syms and syms[ sym_name] != sym_offset: sym_name = sym_name + "_" if sym_name not in syms: syms[sym_name] = sym_offset else: syms[sym_name] = sym_offset add_symbols(fullname, syms) mod.set_symbols(get_symbols(fullname)) return None
def disassemble(addr, cpu_index): ''' Disassemble instruction. Receives 2 parameters: :param addr: Address from instruction to disassemble :type addr: int :param cpu_index: CPU index :type cpu_index: int ''' global logger global num_ins pgd = api.get_running_process(cpu_index) if api.get_os_bits() == 32: md = Cs(CS_ARCH_X86, CS_MODE_32) content = api.r_va(pgd, addr, 0x4) else: md = Cs(CS_ARCH_X86, CS_MODE_64) content = api.r_va(pgd, addr, 0x6) md.detail = True for insn in md.disasm(content, addr): if insn.mnemonic == "call": if len(insn.operands) > 0: mycpu = api.r_cpu(0) simbolo = None if api.get_os_bits() == 32: simbolo = api.va_to_sym(pgd, mycpu.EIP) else: simbolo = api.va_to_sym(pgd, mycpu.RIP) if simbolo != None and api.get_os_bits() != 32: ## Microsoft x64 calling convention ## logger.info( "[API]0x%x:\t%s\t%s\t[RIP]:0x%x\t%s\t[PGD]: %x", insn.address, insn.mnemonic, insn.op_str, mycpu.RIP, simbolo, pgd) num_ins = num_ins + 1 ## RCX - Arg 1 try: rcx_content = api.r_va(pgd, mycpu.RCX, 0x100) logger.info("[RCX]: 0x%x [Data]: %s", mycpu.RCX, smart_format(rcx_content, 0x100, True)) except: logger.info("[RCX]: 0x%x", mycpu.RCX) ## RDX - Arg 2 try: rdx_content = api.r_va(pgd, mycpu.RDX, 0x100) logger.info("[RDX]: 0x%x [Data]: %s", mycpu.RDX, smart_format(rdx_content, 0x100, True)) except: logger.info("[RDX]: 0x%x", mycpu.RDX) ## R8 - Arg 3 try: r8_content = api.r_va(pgd, mycpu.R8, 0x100) logger.info("[R8]: 0x%x [Data]: %s", mycpu.R8, smart_format(r8_content, 0x100, True)) except: logger.info("[R8]: 0x%x", mycpu.R8) ## R9 - Arg 4 try: r9_content = api.r_va(pgd, mycpu.R9, 0x100) logger.info("[R9]: 0x%x [Data]: %s", mycpu.R9, smart_format(r9_content, 0x100, True)) except: logger.info("[R9]: 0x%x", mycpu.R9) ## RAX - return value try: rax_content = api.r_va(pgd, mycpu.RAX, 0x100) logger.info("[RAX]: 0x%x [Data]: %s", mycpu.RAX, smart_format(rax_content, 0x100, True)) except: logger.info("[RAX]: 0x%x", mycpu.RAX) logger.info("--") elif simbolo != None: ## x86 call conventions # cdecl -> arguments are pushed on the stack in the reverse order. EAX return # syscall -> arguments are pushed on the stack right to left. # optlink -> arguments are pushed on the stack right to left. # ... logger.info( "[API]0x%x:\t%s\t%s\t[EIP]:0x%x\t%s\t[PGD]: %x", insn.address, insn.mnemonic, insn.op_str, mycpu.EIP, simbolo, pgd) num_ins = num_ins + 1 bytestoread = 0x200 try: eax_content = api.r_va(pgd, mycpu.EAX, bytestoread) logger.info( "[EAX]: 0x%x [Data]: %s", mycpu.EAX, smart_format(eax_content, bytestoread, True)) except: logger.info("[EAX]: 0x%x", mycpu.EAX) try: ecx_content = api.r_va(pgd, mycpu.ECX, bytestoread) logger.info( "[ECX]: 0x%x [Data]: %s", mycpu.ECX, smart_format(ecx_content, bytestoread, True)) except: logger.info("[ECX]: 0x%x", mycpu.ECX) try: edx_content = api.r_va(pgd, mycpu.EDX, bytestoread) logger.info( "[EDX]: 0x%x [Data]: %s", mycpu.EDX, smart_format(edx_content, bytestoread, True)) except: logger.info("[EDX]: 0x%x", mycpu.EDX) try: ebp_arg2_content = api.r_va(pgd, mycpu.EBP + 8, bytestoread) logger.info( "[EBP+8]: 0x%x [Data]: %s", mycpu.EBP + 8, smart_format(ebp_arg2_content, bytestoread, True)) except: logger.info("[EBP+8]: 0x%x", mycpu.EBP + 8) try: ebp_arg3_content = api.r_va(pgd, mycpu.EBP + 12, bytestoread) logger.info( "[EBP+12]: 0x%x [Data]: %s", mycpu.EBP + 12, smart_format(ebp_arg3_content, bytestoread, True)) except: logger.info("[EBP+12]: 0x%x", mycpu.EBP + 12) try: ebp_arg4_content = api.r_va(pgd, mycpu.EBP + 16, bytestoread) logger.info( "[EBP+16]: 0x%x [Data]: %s", mycpu.EBP + 16, smart_format(ebp_arg4_content, bytestoread, True)) except: logger.info("[EBP+16]: 0x%x", mycpu.EBP + 16) try: ebp_arg5_content = api.r_va(pgd, mycpu.EBP + 20, bytestoread) logger.info( "[EBP+20]: 0x%x [Data]: %s", mycpu.EBP + 20, smart_format(ebp_arg5_content, bytestoread, True)) except: logger.info("[EBP+20]: 0x%x", mycpu.EBP + 20) try: ebp_arg6_content = api.r_va(pgd, mycpu.EBP + 24, bytestoread) logger.info( "[EBP+24]: 0x%x [Data]: %s", mycpu.EBP + 24, smart_format(ebp_arg6_content, bytestoread, True)) except: logger.info("[EBP+24]: 0x%x", mycpu.EBP + 24) logger.info("--")
def dump(params, pid=None, proc=None, update_vads=None, from_addr=None, callback_name=None, terminate_process=False): ''' Dump the process, modules, vads... ''' import volatility.constants as constants import volatility.exceptions as exceptions import volatility.obj as obj import volatility.win32.tasks as tasks from mw_monitor_classes import mwmon from utils import get_addr_space import api TARGET_LONG_SIZE = api.get_os_bits() / 8 cpu_index = params["cpu_index"] cpu = params["cpu"] mwmon.printer("Dumping process...") proc_hdl = None if terminate_process: if TARGET_LONG_SIZE == 4: # ret, proc_hdl, exit_status try: _, proc_hdl, _ = struct.unpack( "<III", api.r_va(api.get_running_process(cpu_index), cpu.ESP, 4 * 3)) except: proc_hdl = 0 mwmon.printer("Could not dereference process handle in dumper.py") elif TARGET_LONG_SIZE == 8: # We don't need the return address # ret = struct.unpack("<Q", # api.r_va(api.get_running_process(cpu_index), cpu.ESP, 8))[0] proc_hdl = cpu.RCX # We don't need the exit status # exit_status = cpu.RDX # It seems there are usually 2 calls, when a process terminates itself. # First, ZwTerminateProcess is called with 0 as proc_hdl, and afterwards # -1. if proc_hdl == 0: return if callback_name is not None: # First, remove callback mwmon.cm.rm_callback(callback_name) # Check if we have been called from the right from_addr if from_addr is not None: if TARGET_LONG_SIZE == 4: try: buff = api.r_va(api.get_running_process(cpu_index), cpu.ESP, 4) ret_addr = struct.unpack("<I", buff)[0] except: ret_addr = 0 mwmon.printer("Could not dereference return address on dumper.py") elif TARGET_LONG_SIZE == 8: try: buff = api.r_va(api.get_running_process(cpu_index), cpu.RSP, 8) ret_addr = struct.unpack("<Q", buff)[0] except: ret_addr = 0 mwmon.printer("Could not dereference return address on dumper.py") if from_addr != ret_addr: return # We have been called from the right point, now, dump. path = mwmon.dumper_path # Dump a file with the VAD info, etc, and a filename for each dumped file, # so that we can import feed IDA with it try: # Dump main executable. addr_space = get_addr_space() # If 1 handle is specified, get the pid for that handle instead # of the calling PID. if proc_hdl is not None: if (TARGET_LONG_SIZE == 4 and proc_hdl == 0xFFFFFFFF) or \ (TARGET_LONG_SIZE == 8 and proc_hdl == 0xFFFFFFFFFFFFFFFF): # If the handle is 0xFFFFFFFF, then the process is the caller. pass else: eprocs = [t for t in tasks.pslist( addr_space) if t.UniqueProcessId == pid] proc_obj = None # Search handle table for the new created process for task in eprocs: if task.UniqueProcessId == pid and task.ObjectTable.HandleTableList: for handle in task.ObjectTable.handles(): if handle.is_valid() and handle.HandleValue == proc_hdl and handle.get_object_type() == "Process": proc_obj = handle.dereference_as("_EPROCESS") break break if proc_obj is not None: # If we found the handle to which it referred, update the # corresponding pid pid = int(proc_obj.UniqueProcessId) else: return # Case when no PID is specified, just dump everything if pid is None: pids = [p.pid for p in mwmon.data.procs] # When one pid is specified, dump that PID else: pids = [pid] eprocs = [t for t in tasks.pslist( addr_space) if t.UniqueProcessId in pids] for task in eprocs: mwmon.printer("Dumping process %x" % (task.UniqueProcessId)) # Code adapted from procdump (volatility) task_space = task.get_process_address_space() if task_space is None: mwmon.printer("Error: Cannot acquire process AS") return elif task.Peb is None: # we must use m() here, because any other attempt to # reference task.Peb will try to instantiate the _PEB mwmon.printer( "Error: PEB at {0:#x} is unavailable (possibly due to paging)".format(task.m('Peb'))) return elif task_space.vtop(task.Peb.ImageBaseAddress) is None: mwmon.printer( "Error: ImageBaseAddress at {0:#x} is unavailable" + "(possibly due to paging)".format(task.Peb.ImageBaseAddress)) return else: mwmon.printer( "Dumping executable for %x" % (task.UniqueProcessId)) dump_file = os.path.join( path, "executable.%x.exe" % (task.UniqueProcessId)) of = open(dump_file, 'wb') pe_file = obj.Object( "_IMAGE_DOS_HEADER", offset=task.Peb.ImageBaseAddress, vm=task_space) try: for offset, code in pe_file.get_image(unsafe=True, memory=False, fix=True): of.seek(offset) of.write(code) except ValueError, ve: mwmon.printer("Error: {0}".format(ve)) return except exceptions.SanityCheckException, ve: mwmon.printer("Error: {0} Try -u/--unsafe".format(ve)) return