def module_change_callback(pgd, bp_vaddr, bp_haddr, cpu_index, vaddr, size, haddr, data): ''' Callback function triggered whenever there is a change in the list of linked modules for a given process. Updates the module list for that PGD (or kernel). ''' import functools import struct import api from api import BP from vmi import update_modules from utils import pp_error from vmi import set_modules_non_present from vmi import clean_non_present_modules # First, we check if the memory address written points to a module # that we have already detected (it is in our list of hooking points). # In this way we avoid triggering one update operation for every # modified pointer (inserting a module requires to change several # pointers, due to the different linked lists). # Return if it points inside a module that we have already added to our list for module_base, hook_addr, hook_size in module_load_remove_breakpoints[pgd]: if data >= module_base and data < (hook_addr + hook_size): return hooking_points = update_modules(pgd) if hooking_points is not None: # Update hooking points # 1) Remove the breakpoints not used anymore bps_to_remove = [] for hp in module_load_remove_breakpoints[pgd]: if hp not in hooking_points: bps_to_remove.append(hp) for hp in bps_to_remove: module_load_remove_breakpoints[pgd][hp].disable() del module_load_remove_breakpoints[pgd][hp] # 2) Add new breakpoints for hp in hooking_points: if hp not in module_load_remove_breakpoints[pgd]: module_base, addr, size = hp haddr = api.va_to_pa(pgd, addr) bp = BP(haddr, None, size = size, typ = BP.MEM_WRITE_PHYS, func = functools.partial(module_change_callback, pgd, addr, haddr)) module_load_remove_breakpoints[pgd][hp] = bp bp.enable() else: # Just remove all the modules for the process # Mark all modules as non-present set_modules_non_present(None, pgd) # Remove all the modules that are not marked as present clean_non_present_modules(None, pgd)
def windows_update_modules(pgd, update_symbols=False): ''' Use volatility to get the modules and symbols for a given process, and update the cache accordingly ''' global last_kdbg import api from utils import get_addr_space from vmi import modules from vmi import set_modules_non_present from vmi import clean_non_present_modules if pgd != 0: addr_space = get_addr_space(pgd) else: addr_space = get_addr_space() if addr_space is None: pp_error("Volatility address space not loaded\n") return [] # Get EPROC directly from its offset procs = api.get_process_list() inserted_bases = [] # Parse/update kernel modules if pgd 0 is requested: if pgd == 0 and last_kdbg is not None: if (0, 0) not in modules: modules[(0, 0)] = {} kdbg = obj.Object("_KDDEBUGGER_DATA64", offset=last_kdbg, vm=addr_space) # List entries are returned, so that # we can monitor memory writes to these # entries and detect when a module is added # or removed list_entry_size = None list_entry_regions = [] # Add the initial list pointer as a list entry list_entry_regions.append( (kdbg.obj_offset, kdbg.PsLoadedModuleList.obj_offset, kdbg.PsLoadedModuleList.size())) # Mark all modules as non-present set_modules_non_present(0, 0) for module in kdbg.modules(): if module.DllBase not in inserted_bases: inserted_bases.append(module.DllBase) windows_insert_module(0, 0, module, update_symbols) if list_entry_size is None: list_entry_size = module.InLoadOrderLinks.size() list_entry_regions.append( (module.obj_offset, module.InLoadOrderLinks.obj_offset, list_entry_size * 3)) # Remove all the modules that are not marked as present clean_non_present_modules(0, 0) return list_entry_regions for proc in procs: p_pid = proc["pid"] p_pgd = proc["pgd"] # p_name = proc["name"] p_kernel_addr = proc["kaddr"] if p_pgd == pgd: task = obj.Object("_EPROCESS", offset=p_kernel_addr, vm=addr_space) # List entries are returned, so that # we can monitor memory writes to these # entries and detect when a module is added # or removed list_entry_size = None list_entry_regions = [] if task.Peb is None or not task.Peb.is_valid(): if isinstance(task.Peb.obj_offset, int): list_entry_regions.append( (task.obj_offset, task.Peb.obj_offset, task.Peb.size())) return list_entry_regions if task.Peb.Ldr is None or not task.Peb.Ldr.is_valid(): list_entry_regions.append( (task.Peb.v(), task.Peb.Ldr.obj_offset, task.Peb.Ldr.size())) return list_entry_regions # Add the initial list pointer as a list entry if we already have a PEB and LDR list_entry_regions.append( (task.Peb.Ldr.dereference().obj_offset, task.Peb.Ldr.InLoadOrderModuleList.obj_offset, task.Peb.Ldr.InLoadOrderModuleList.size() * 3)) # Note: we do not erase the modules we have information for from the list, # unless we have a different module loaded at the same base address. # In this way, if at some point the module gets unmapped from the PEB list # but it is still in memory, we do not loose the information. if (p_pid, p_pgd) not in modules: modules[(p_pid, p_pgd)] = {} # Mark all modules as non-present set_modules_non_present(p_pid, p_pgd) for module in task.get_init_modules(): if module.DllBase not in inserted_bases: inserted_bases.append(module.DllBase) windows_insert_module(p_pid, p_pgd, module, update_symbols) if list_entry_size is None: list_entry_size = module.InLoadOrderLinks.size() list_entry_regions.append( (module.obj_offset, module.InLoadOrderLinks.obj_offset, list_entry_size * 3)) for module in task.get_mem_modules(): if module.DllBase not in inserted_bases: inserted_bases.append(module.DllBase) windows_insert_module(p_pid, p_pgd, module, update_symbols) if list_entry_size is None: list_entry_size = module.InLoadOrderLinks.size() list_entry_regions.append( (module.obj_offset, module.InLoadOrderLinks.obj_offset, list_entry_size * 3)) for module in task.get_load_modules(): if module.DllBase not in inserted_bases: inserted_bases.append(module.DllBase) windows_insert_module(p_pid, p_pgd, module, update_symbols) if list_entry_size is None: list_entry_size = module.InLoadOrderLinks.size() list_entry_regions.append( (module.obj_offset, module.InLoadOrderLinks.obj_offset, list_entry_size * 3)) # Remove all the modules that are not marked as present clean_non_present_modules(p_pid, p_pgd) return list_entry_regions return None
def linux_update_modules(pgd, update_symbols=False): from utils import ConfigurationManager as conf_m import volatility.obj as obj from vmi import set_modules_non_present from vmi import clean_non_present_modules if conf_m.addr_space is None: linux_init_address_space() # pgd == 0 means that kernel modules have been requested if pgd == 0: # List entries are returned, so that # we can monitor memory writes to these # entries and detect when a module is added # or removed list_entry_size = None list_entry_regions = [] # Now, update the kernel modules modules_addr = conf_m.addr_space.profile.get_symbol("modules") modules = obj.Object("list_head", vm=conf_m.addr_space, offset=modules_addr) # Add the initial list pointer as a list entry # modules_addr is the offset of a list_head (2 pointers) that points to the # first entry of a module list of type module. list_entry_regions.append((modules_addr, modules_addr, modules.size())) # Mark all modules as non-present set_modules_non_present(0, 0) for module in modules.list_of_type("module", "list"): """ pp_debug("Module: %s - %x - %x - %x - %x - %x\n" % (module.name, module.obj_offset, module.module_init, module.init_size, module.module_core, module.core_size)) secs = [] for section in module.get_sections(): secs.append({"name": section.sect_name, "addr": section.address }) for section in sorted(secs, key = lambda k: k["addr"]): pp_debug(" %s - %x\n" % (section["name"],section["addr"])) """ if list_entry_size is None: list_entry_size = module.list.size() # The 'module' type has a field named list of type list_head, that points # to the next module in the linked list. entry = (module.obj_offset, module.list.obj_offset, list_entry_size) if entry not in list_entry_regions: list_entry_regions.append(entry) # First, create a module for the "module_core", that contains # .text, readonly data and writable data if module.module_core != 0 and module.core_size != 0: linux_insert_kernel_module(module, long(module.module_core.v()), long(module.core_size.v()), str(module.name), str(module.name), update_symbols) # Now, check if there is "module_init" region, which will contain init sections such as .init.text , init # readonly and writable data... if module.module_init != 0 and module.init_size != 0: linux_insert_kernel_module(module, module.module_init.v(), module.init_size.v(), module.name + "/module_init", module.name + "/module_init", update_symbols) else: # If there is no module_init, check if there is any section # outside the module_core region secs = [] for section in module.get_sections(): if section.address < module.module_core or section.address >= ( module.module_core + module.core_size): secs.append(section) if len(secs) > 0: # Now, compute the range of sections and put them into a # module_init module block secs = sorted(secs, key=lambda k: k.address) start = secs[0].address # Address of the last section + 0x4000 cause we do not know # the size size = (secs[-1].address + 0x4000) - secs[0].address linux_insert_kernel_module(module, start, size, module.name + "/module_init", module.name + "/module_init", update_symbols) # Remove all the modules that are not marked as present clean_non_present_modules(0, 0) return list_entry_regions # If pgd != 0 was requested tasks = [] init_task_addr = conf_m.addr_space.profile.get_symbol("init_task") init_task = obj.Object("task_struct", vm=conf_m.addr_space, offset=init_task_addr) # walk the ->tasks list, note that this will *not* display "swapper" for task in init_task.tasks: tasks.append(task) # List of tasks (threads) whose pgd is equal to the pgd to update tasks_to_update = [] # First task in the list with a valid pgd for task in tasks: # Certain kernel threads do not have a memory map (they just take the pgd / memory map of # the thread that was previously executed, because the kernel is mapped # in all the threads. if task.mm: phys_pgd = conf_m.addr_space.vtop(task.mm.pgd) or task.mm.pgd if phys_pgd == pgd: tasks_to_update.append(task) # List entries are returned, so that # we can monitor memory writes to these # entries and detect when a module is added # or removed list_entry_size = None list_entry_regions = [] for task in tasks_to_update: phys_pgd = conf_m.addr_space.vtop(task.mm.pgd) or task.mm.pgd # Mark all modules as non-present set_modules_non_present(task.pid.v(), phys_pgd) # Add the initial list pointer as a list entry list_entry_regions.append( (task.mm.mmap.obj_offset, task.mm.mmap.obj_offset, task.mm.mmap.size())) for vma in task.get_proc_maps(): if list_entry_size is None: list_entry_size = vma.vm_next.size() entry = (vma.obj_offset, vma.vm_next.obj_offset, list_entry_size) if entry not in list_entry_regions: list_entry_regions.append(entry) (fname, major, minor, ino, pgoff) = vma.info(task) # Only add the module if the inode is not 0 (it is an actual module # and not a heap region if ino != 0: # Checksum linux_insert_module( task, task.pid.v(), Address(phys_pgd), Address(vma.vm_start), Address(vma.vm_end) - Address(vma.vm_start), os.path.basename(fname), fname, update_symbols) # Remove all the modules that are not marked as present clean_non_present_modules(task.pid.v(), phys_pgd) return list_entry_regions
def linux_update_modules(pgd, update_symbols=False): from utils import ConfigurationManager as conf_m import volatility.obj as obj from vmi import set_modules_non_present from vmi import clean_non_present_modules if conf_m.addr_space is None: linux_init_address_space() # pgd == 0 means that kernel modules have been requested if pgd == 0: # List entries are returned, so that # we can monitor memory writes to these # entries and detect when a module is added # or removed list_entry_size = None list_entry_regions = [] # Now, update the kernel modules modules_addr = conf_m.addr_space.profile.get_symbol("modules") modules = obj.Object( "list_head", vm=conf_m.addr_space, offset=modules_addr) # Add the initial list pointer as a list entry # modules_addr is the offset of a list_head (2 pointers) that points to the # first entry of a module list of type module. list_entry_regions.append((modules_addr, modules_addr, modules.size())) # Mark all modules as non-present set_modules_non_present(0, 0) for module in modules.list_of_type("module", "list"): """ pp_debug("Module: %s - %x - %x - %x - %x - %x\n" % (module.name, module.obj_offset, module.module_init, module.init_size, module.module_core, module.core_size)) secs = [] for section in module.get_sections(): secs.append({"name": section.sect_name, "addr": section.address }) for section in sorted(secs, key = lambda k: k["addr"]): pp_debug(" %s - %x\n" % (section["name"],section["addr"])) """ if list_entry_size is None: list_entry_size = module.list.size() # The 'module' type has a field named list of type list_head, that points # to the next module in the linked list. entry = (module.obj_offset, module.list.obj_offset, list_entry_size) if entry not in list_entry_regions: list_entry_regions.append(entry) # First, create a module for the "module_core", that contains # .text, readonly data and writable data if module.module_core != 0 and module.core_size != 0: linux_insert_kernel_module(module, long(module.module_core.v()), long( module.core_size.v()), str(module.name), str(module.name), update_symbols) # Now, check if there is "module_init" region, which will contain init sections such as .init.text , init # readonly and writable data... if module.module_init != 0 and module.init_size != 0: linux_insert_kernel_module(module, module.module_init.v(), module.init_size.v(), module.name + "/module_init", module.name + "/module_init", update_symbols) else: # If there is no module_init, check if there is any section # outside the module_core region secs = [] for section in module.get_sections(): if section.address < module.module_core or section.address >= (module.module_core + module.core_size): secs.append(section) if len(secs) > 0: # Now, compute the range of sections and put them into a # module_init module block secs = sorted(secs, key=lambda k: k.address) start = secs[0].address # Address of the last section + 0x4000 cause we do not know # the size size = (secs[-1].address + 0x4000) - secs[0].address linux_insert_kernel_module(module, start, size, module.name + "/module_init", module.name + "/module_init", update_symbols) # Remove all the modules that are not marked as present clean_non_present_modules(0, 0) return list_entry_regions # If pgd != 0 was requested tasks = [] init_task_addr = conf_m.addr_space.profile.get_symbol("init_task") init_task = obj.Object( "task_struct", vm=conf_m.addr_space, offset=init_task_addr) # walk the ->tasks list, note that this will *not* display "swapper" for task in init_task.tasks: tasks.append(task) # List of tasks (threads) whose pgd is equal to the pgd to update tasks_to_update = [] # First task in the list with a valid pgd for task in tasks: # Certain kernel threads do not have a memory map (they just take the pgd / memory map of # the thread that was previously executed, because the kernel is mapped # in all the threads. if task.mm: phys_pgd = conf_m.addr_space.vtop(task.mm.pgd) or task.mm.pgd if phys_pgd == pgd: tasks_to_update.append(task) # List entries are returned, so that # we can monitor memory writes to these # entries and detect when a module is added # or removed list_entry_size = None list_entry_regions = [] for task in tasks_to_update: phys_pgd = conf_m.addr_space.vtop(task.mm.pgd) or task.mm.pgd # Mark all modules as non-present set_modules_non_present(task.pid.v(), phys_pgd) # Add the initial list pointer as a list entry list_entry_regions.append((task.mm.mmap.obj_offset, task.mm.mmap.obj_offset, task.mm.mmap.size())) for vma in task.get_proc_maps(): if list_entry_size is None: list_entry_size = vma.vm_next.size() entry = (vma.obj_offset, vma.vm_next.obj_offset, list_entry_size) if entry not in list_entry_regions: list_entry_regions.append(entry) (fname, major, minor, ino, pgoff) = vma.info(task) # Only add the module if the inode is not 0 (it is an actual module # and not a heap region if ino != 0: # Checksum linux_insert_module(task, task.pid.v(), Address(phys_pgd), Address(vma.vm_start), Address(vma.vm_end) - Address( vma.vm_start), os.path.basename(fname), fname, update_symbols) # Remove all the modules that are not marked as present clean_non_present_modules(task.pid.v(), phys_pgd) return list_entry_regions
def windows_update_modules(pgd, update_symbols=False): ''' Use volatility to get the modules and symbols for a given process, and update the cache accordingly ''' global last_kdbg global symbol_cache_must_be_saved import api from utils import get_addr_space from vmi import set_modules_non_present from vmi import clean_non_present_modules from vmi import add_module from vmi import get_module from vmi import has_module if pgd != 0: addr_space = get_addr_space(pgd) else: addr_space = get_addr_space() if addr_space is None: pp_error("Volatility address space not loaded\n") return [] # Get EPROC directly from its offset procs = api.get_process_list() inserted_bases = [] # Parse/update kernel modules if pgd 0 is requested: if pgd == 0 and last_kdbg is not None: kdbg = obj.Object( "_KDDEBUGGER_DATA64", offset=last_kdbg, vm=addr_space) # List entries are returned, so that # we can monitor memory writes to these # entries and detect when a module is added # or removed list_entry_size = None list_entry_regions = [] # Add the initial list pointer as a list entry list_entry_regions.append((kdbg.obj_offset, kdbg.PsLoadedModuleList.obj_offset, kdbg.PsLoadedModuleList.size())) # Mark all modules as non-present set_modules_non_present(0, 0) for module in kdbg.modules(): if module.DllBase not in inserted_bases: inserted_bases.append(module.DllBase) windows_insert_module(0, 0, module, update_symbols) if list_entry_size is None: list_entry_size = module.InLoadOrderLinks.size() list_entry_regions.append((module.obj_offset, module.InLoadOrderLinks.obj_offset, list_entry_size * 3)) # Remove all the modules that are not marked as present clean_non_present_modules(0, 0) if symbol_cache_must_be_saved: from vmi import save_symbols_to_cache_file save_symbols_to_cache_file() symbol_cache_must_be_saved = False return list_entry_regions for proc in procs: p_pid = proc["pid"] p_pgd = proc["pgd"] # p_name = proc["name"] p_kernel_addr = proc["kaddr"] if p_pgd == pgd: task = obj.Object("_EPROCESS", offset=p_kernel_addr, vm=addr_space) # List entries are returned, so that # we can monitor memory writes to these # entries and detect when a module is added # or removed list_entry_size = None list_entry_regions = [] scan_peb = True if task.Peb is None or not task.Peb.is_valid(): if isinstance(task.Peb.obj_offset, int): list_entry_regions.append((task.obj_offset, task.Peb.obj_offset, task.Peb.size())) scan_peb = False if task.Peb.Ldr is None or not task.Peb.Ldr.is_valid(): list_entry_regions.append((task.Peb.v(), task.Peb.Ldr.obj_offset, task.Peb.Ldr.size())) scan_peb = False if scan_peb: # Add the initial list pointer as a list entry if we already have a PEB and LDR list_entry_regions.append((task.Peb.Ldr.dereference().obj_offset, task.Peb.Ldr.InLoadOrderModuleList.obj_offset, task.Peb.Ldr.InLoadOrderModuleList.size() * 3)) # Note: we do not erase the modules we have information for from the list, # unless we have a different module loaded at the same base address. # In this way, if at some point the module gets unmapped from the PEB list # but it is still in memory, we do not loose the information. # Mark all modules as non-present set_modules_non_present(p_pid, p_pgd) for module in task.get_init_modules(): if module.DllBase not in inserted_bases: inserted_bases.append(module.DllBase) windows_insert_module(p_pid, p_pgd, module, update_symbols) if list_entry_size is None: list_entry_size = module.InLoadOrderLinks.size() list_entry_regions.append((module.obj_offset, module.InLoadOrderLinks.obj_offset, list_entry_size * 3)) for module in task.get_mem_modules(): if module.DllBase not in inserted_bases: inserted_bases.append(module.DllBase) windows_insert_module(p_pid, p_pgd, module, update_symbols) if list_entry_size is None: list_entry_size = module.InLoadOrderLinks.size() list_entry_regions.append((module.obj_offset, module.InLoadOrderLinks.obj_offset, list_entry_size * 3)) for module in task.get_load_modules(): if module.DllBase not in inserted_bases: inserted_bases.append(module.DllBase) windows_insert_module(p_pid, p_pgd, module, update_symbols) if list_entry_size is None: list_entry_size = module.InLoadOrderLinks.size() list_entry_regions.append((module.obj_offset, module.InLoadOrderLinks.obj_offset, list_entry_size * 3)) # Now, if we are a 64bit system and the process is a Wow64 process, traverse VAD # to find the 32 bit modules if api.get_os_bits() == 64 and task.IsWow64: for vad in task.VadRoot.traverse(): if vad is not None: if hasattr(vad, "FileObject"): f = vad.FileObject if f is not None: fname = f.file_name_with_device() if fname and "Windows\\SysWOW64".lower() in fname.lower() and ".dll" == fname[-4:].lower(): fname_starts = fname.find("Windows\\SysWOW64") fname = fname[fname_starts:] windows_insert_module_internal(p_pid, p_pgd, vad.Start, vad.End - vad.Start, fname, fname.split("\\")[-1], "", update_symbols, do_stop = True) # Remove all the modules that are not marked as present clean_non_present_modules(p_pid, p_pgd) if symbol_cache_must_be_saved: from vmi import save_symbols_to_cache_file save_symbols_to_cache_file() symbol_cache_must_be_saved = False return list_entry_regions return None
def module_change_callback(pgd, bp_vaddr, bp_haddr, params): ''' Callback function triggered whenever there is a change in the list of linked modules for a given process. Updates the module list for that PGD (or kernel). ''' import functools import struct import api from api import BP from vmi import update_modules from utils import pp_error from vmi import set_modules_non_present from vmi import clean_non_present_modules cpu_index = params["cpu_index"] vaddr = params["vaddr"] size = params["size"] haddr = params["haddr"] data = params["data"] # First, we check if the memory address written points to a module # that we have already detected (it is in our list of hooking points). # In this way we avoid triggering one update operation for every # modified pointer (inserting a module requires to change several # pointers, due to the different linked lists). # Return if it points inside a module that we have already added to our list for module_base, hook_addr, hook_size in module_load_remove_breakpoints[pgd]: if data >= module_base and data < (hook_addr + hook_size): return hooking_points = update_modules(pgd) if hooking_points is not None: # Update hooking points # 1) Remove the breakpoints not used anymore bps_to_remove = [] for hp in module_load_remove_breakpoints[pgd]: if hp not in hooking_points: bps_to_remove.append(hp) for hp in bps_to_remove: module_load_remove_breakpoints[pgd][hp].disable() del module_load_remove_breakpoints[pgd][hp] # 2) Add new breakpoints for hp in hooking_points: if hp not in module_load_remove_breakpoints[pgd]: module_base, addr, size = hp haddr = api.va_to_pa(pgd, addr) bp = BP(haddr, None, size = size, typ = BP.MEM_WRITE_PHYS, func = functools.partial(module_change_callback, pgd, addr, haddr), new_style = True) module_load_remove_breakpoints[pgd][hp] = bp bp.enable() else: # Just remove all the modules for the process # Mark all modules as non-present set_modules_non_present(None, pgd) # Remove all the modules that are not marked as present clean_non_present_modules(None, pgd)