class ProcessChecker: whlte_list = ['sshd', 'insmod', 'kworker/0:2', 'kworker/u2:2'] def __init__(self, vm, callback=None, interval=10): self._vm = vm self._callback = callback self._init_vmi() self.logger = setup_logger( self._vm.name, self._vm.name + '/' + self._vm.name + '.log', logging.INFO) self._ori_ps_list = self._get_process_list() self._ori_ps_set = set(self._ori_ps_list.keys()) self._timer = RepeatableTimer(interval, self.check_process) self._dump_enabled = False def _init_vmi(self): """ Initialize LibVMI """ self._vmi = Libvmi(self._vm.name) # get ostype self._os = self._vmi.get_ostype() # init offsets values self._tasks_offset = None self._name_offset = None self._pid_offset = None if self._os == VMIOS.LINUX: self._tasks_offset = self._vmi.get_offset("linux_tasks") self._name_offset = self._vmi.get_offset("linux_name") self._pid_offset = self._vmi.get_offset("linux_pid") elif self._os == VMIOS.WINDOWS: self._tasks_offset = self._vmi.get_offset("win_tasks") self._name_offset = self._vmi.get_offset("win_pname") self._pid_offset = self._vmi.get_offset("win_pid") else: self.logger.error("Unknown OS") return 0 return 1 def _get_process_list(self): """ Get the process list inside the VM :return: process list of getting the page successfully or None. """ # pause vm with pause(self._vmi): # demonstrate name and id accessors name = self._vmi.get_name() id = self._vmi.get_vmid() self.logger.debug("Process listing for VM %s (id: %s)", name, id) if self._os == VMIOS.LINUX: list_head = self._vmi.translate_ksym2v("init_task") list_head += self._tasks_offset elif self._os == VMIOS.WINDOWS: list_head = self._vmi.read_addr_ksym("PsActiveProcessHead") else: self.logger.error("Unknown OS") return None process_list = dict() cur_list_entry = list_head next_list_entry = self._vmi.read_addr_va(cur_list_entry, 0) while True: current_process = cur_list_entry - self._tasks_offset pid = self._vmi.read_32_va(current_process + self._pid_offset, 0) procname = self._vmi.read_str_va( current_process + self._name_offset, 0) process_list[pid] = (procname, hex(current_process)) self.logger.debug("[%s] %s (struct addr:%s)", pid, procname, hex(current_process)) cur_list_entry = next_list_entry next_list_entry = self._vmi.read_addr_va(cur_list_entry, 0) if self._os == VMIOS.WINDOWS and next_list_entry == list_head: break elif self._os == VMIOS.LINUX and cur_list_entry == list_head: break return process_list def check_process(self): self._timer.start() ps_list = self._get_process_list() #ps_list = self._filter_white_list(ps_list) ps_set = set(ps_list.keys()) new_ps = ps_set - self._ori_ps_set reduce_ps = self._ori_ps_set - ps_set if new_ps: for pid in new_ps: if ps_list[pid][0] not in self.whlte_list: if not self._vm.dump_enabled: self._vm.dump_enabled = True self._callback() self.logger.warning( "detected new process. [%s] %s (struct addr:%s)", pid, ps_list[pid][0], ps_list[pid][1]) if reduce_ps: for pid in reduce_ps: if self._ori_ps_list[pid][0] not in self.whlte_list: self.logger.warning( "detected process leave. [%s] %s (struct addr:%s)", pid, self._ori_ps_list[pid][0], self._ori_ps_list[pid][1]) self._ori_ps_list = ps_list self._ori_ps_set = ps_set def start(self): """Start a thread that compares both of origin process list and a new one""" self._timer.start() def stop(self): """stop process checker""" self._timer.cancel() self._vmi.destroy()
class DebugContext: def __init__(self, vm_name, process_name): self.log = logging.getLogger(__class__.__name__) self.vm_name = vm_name self.full_system_mode = False self.target_name = process_name self.target_pid = None if process_name is None: self.full_system_mode = True self.target_name = 'kernel' # kernel space is represented by PID 0 in LibVMI self.target_pid = 0 self.target_dtb = None self.vmi = Libvmi(self.vm_name, INIT_DOMAINNAME | INIT_EVENTS) self.kernel_base = self.get_kernel_base() if self.kernel_base: logging.info('kernel base address: %s', hex(self.kernel_base)) def get_kernel_base(self): if self.vmi.get_ostype() == VMIOS.LINUX: return self.vmi.translate_ksym2v('start_kernel') if self.vmi.get_ostype() == VMIOS.WINDOWS: # small hack with rekall JSON profile to get the kernel base address # LibVMI should provide an API to query it profile_path = self.vmi.get_rekall_path() if not profile_path: raise RuntimeError('Cannot get rekall profile from LibVMI') with open(profile_path) as f: profile = json.load(f) ps_head_rva = profile['$CONSTANTS']['PsActiveProcessHead'] ps_head_va = self.vmi.translate_ksym2v('PsActiveProcessHead') return ps_head_va - ps_head_rva return None def attach(self): self.log.info('attaching on %s', self.target_name) # VM must be running self.vmi.pause_vm() if self.full_system_mode: # no need to intercept a specific process regs = self.vmi.get_vcpuregs(0) self.target_dtb = regs[X86Reg.CR3] return cb_data = {'interrupted': False} def cb_on_cr3_load(vmi, event): pname = dtb_to_pname(vmi, event.cffi_event.reg_event.value) self.log.info('intercepted %s', pname) pattern = re.escape(self.target_name) if re.match(pattern, pname, re.IGNORECASE): vmi.pause_vm() self.target_dtb = event.cffi_event.reg_event.value self.target_pid = vmi.dtb_to_pid(self.target_dtb) cb_data['interrupted'] = True reg_event = RegEvent(X86Reg.CR3, RegAccess.W, cb_on_cr3_load) self.vmi.register_event(reg_event) self.vmi.resume_vm() while not cb_data['interrupted']: self.vmi.listen(1000) # clear queue self.vmi.listen(0) # clear event self.vmi.clear_event(reg_event) def detach(self): try: logging.info('resuming VM execution') self.vmi.resume_vm() except LibvmiError: # already in running state pass self.vmi.destroy()