def calculate(self): common.set_plugin_members(self) if self._config.SYSCALL_INDEXES: index_names = self._parse_handler_names() else: index_names = None sym_addrs = self.profile.get_all_addresses() table_addr = self.addr_space.profile.get_symbol("_sysent") nsysent = obj.Object( "int", offset=self.addr_space.profile.get_symbol("_nsysent"), vm=self.addr_space) sysents = obj.Object(theType="Array", offset=table_addr, vm=self.addr_space, count=nsysent, targetType="sysent") for (i, sysent) in enumerate(sysents): ent_addr = sysent.sy_call.v() hooked = ent_addr not in sym_addrs if index_names: sym_name = index_names[i] else: sym_name = self.profile.get_symbol_by_address( "kernel", ent_addr) if not sym_name: sym_name = "N/A" yield (table_addr, "SyscallTable", i, ent_addr, hooked, sym_name)
def calculate(self): common.set_plugin_members(self) if self._config.SYSCALL_INDEXES: index_names = self._parse_handler_names() else: index_names = None sym_addrs = self.profile.get_all_addresses() table_addr = self.addr_space.profile.get_symbol("_sysent") nsysent = obj.Object("int", offset = self.addr_space.profile.get_symbol("_nsysent"), vm = self.addr_space) sysents = obj.Object(theType = "Array", offset = table_addr, vm = self.addr_space, count = nsysent, targetType = "sysent") for (i, sysent) in enumerate(sysents): ent_addr = sysent.sy_call.v() hooked = ent_addr not in sym_addrs if index_names: sym_name = index_names[i] else: sym_name = self.profile.get_symbol_by_address("kernel", ent_addr) if not sym_name: sym_name = "N/A" yield (table_addr, "SyscallTable", i, ent_addr, hooked, sym_name)
def calculate(self): args = () common.set_plugin_members(self) sym_addrs = self.profile.get_all_addresses() nsysent = obj.Object("int", offset = self.addr_space.profile.get_symbol("_nsysent"), vm = self.addr_space) sysents = obj.Object(theType = "Array", offset = self.addr_space.profile.get_symbol("_sysent"), vm = self.addr_space, count = nsysent, targetType = "sysent") for (i, sysent) in enumerate(sysents): hooked = False ent_addr = sysent.sy_call.v() syscall_name = self.profile.get_symbol_by_address("kernel", ent_addr) if 'dtrace' in syscall_name: # syscall probes: http://siliconblade.blogspot.com/2013/04/hunting-d-trace-rootkits-with.html hooked = "syscall_probe" else: # fbt probes: http://reverse.put.as/2013/05/07/syscan13-revisiting-mac-os-x-rootkits-presentation/ data = sysent.obj_vm.zread(ent_addr + 1, 2) if binascii.hexlify(data) == 'f089': hooked = 'fbt_probe' if hooked != False: yield ('Syscall_Table', i, ent_addr, syscall_name, hooked) # mach_trap probes: http://felinemenace.org/~nemo/dtrace-infiltrate.pdf t = trap.mac_check_trap_table(self._config, args) for (table_addr, table_name, index, call_addr, sym_name, other_hooked) in t.calculate(): if other_hooked == True: sym_name = "HOOKED" if 'dtrace' in sym_name: hooked = "mach_trap_probe" yield ('Trap_Table', index, call_addr, sym_name, hooked)
def calculate(self): common.set_plugin_members(self) sym_addrs = self.profile.get_all_addresses() table_addr = self.addr_space.profile.get_symbol("_sysent") nsysent = obj.Object( "int", offset=self.addr_space.profile.get_symbol("_nsysent"), vm=self.addr_space) sysents = obj.Object(theType="Array", offset=table_addr, vm=self.addr_space, count=nsysent, targetType="sysent") for (i, sysent) in enumerate(sysents): ent_addr = sysent.sy_call.v() hooked = ent_addr not in sym_addrs if hooked == False: sym_name = self.profile.get_symbol_by_address( "kernel", ent_addr) else: sym_name = "HOOKED" yield (table_addr, "SyscallTable", i, ent_addr, hooked, sym_name)
def calculate(self): common.set_plugin_members(self) model = self.addr_space.profile.metadata.get('memory_model', 0) if model == '32bit': distorm_mode = distorm3.Decode32Bits else: distorm_mode = distorm3.Decode64Bits # get kernel start, end kp = self.addr_space.profile.get_symbol("_g_kernel_kmod_info") kmodk = obj.Object("kmod_info", offset = kp, vm = self.addr_space) kernel_start = kmodk.address kernel_end = kernel_start + kmodk.m('size') # check if kext functions are modified, including kernel symbols if self._config.CHECKKEXTS or self._config.CHECKKERNEL: # get symbols from kext __TEXT in memory rather than file kext_addr_list = [] # get kernel address kmod = obj.Object("kmod_info", offset = self.addr_space.profile.get_symbol("_g_kernel_kmod_info"), vm = self.addr_space) kext_addr_list.append(('__kernel__', kmod.address, kmod.m('size'))) # get all kexts if self._config.CHECKKERNEL == False: # get other kext addresses p = self.addr_space.profile.get_symbol("_kmod") kmodaddr = obj.Object("Pointer", offset = p, vm = self.addr_space) kmod = kmodaddr.dereference_as("kmod_info") while kmod.is_valid(): kext_addr_list.append((kmod.name, kmod.address, kmod.m('size'))) kmod = kmod.next # loop thru kexts for kext_name, kext_address, kext_size in kext_addr_list: k_start = kext_address k_end = kext_address + kext_size #loop thru kext functions for func_name, func_addr in self.getKextSymbols(kext_addr = kext_address, onlyFunctions = True, fmodel = model): # false positive, remove if needed if func_name in ["pthreads_dummy_symbol"]: continue # check if function's been modified modified, dst_addr, op_addr = self.isCallReferenceModified(model, distorm_mode, func_addr, k_start, k_end) if modified: if dst_addr != None: hook_kext = self.findKextWithAddress(dst_addr) else: hook_kext = kext_name yield (func_addr, func_name, op_addr, dst_addr, hook_kext)
def calculate(self): common.set_plugin_members(self) model = self.addr_space.profile.metadata.get('memory_model', 0) if model == '32bit': distorm_mode = distorm3.Decode32Bits else: distorm_mode = distorm3.Decode64Bits # get kernel start, end kp = self.addr_space.profile.get_symbol("_g_kernel_kmod_info") kmodk = obj.Object("kmod_info", offset = kp, vm = self.addr_space) k_start = kmodk.address k_end = k_start + kmodk.m('size') # check if trustedbsd mac_policy_list is shadowed is_shadowed = False shadow_addr = None # some functions with MAC_CHECK/mac_policy_list references: mac_proc_check_get_task_name, mac_proc_check_get_task, mac_proc_check_fork, mac_cred_check_label_update, mac_cred_check_visible, mac_proc_check_debug, mac_proc_check_run_cs_invalid, mac_proc_check_sched, mac_proc_check_signal, mac_proc_check_wait, mac_proc_check_setlcid, mac_proc_check_getlcid, mac_lctx_check_label_update, mac_proc_check_suspend_resume, mac_port_check_service, mac_port_label_compute, mac_file_check_create, mac_file_check_dup, mac_file_check_fcntl, mac_file_check_ioctl, mac_file_check_inherit, mac_file_check_receive, mac_file_check_get_offset, mac_file_check_change_offset, mac_file_check_get, mac_file_check_set, mac_file_check_lock, mac_file_check_mmap original_mpl_addr = self.addr_space.profile.get_symbol("_mac_policy_list") original_mpl = obj.Object("mac_policy_list", offset = original_mpl_addr, vm = self.addr_space) # to get the disassembly of MAC_CHECK, disassemble mac_proc_check_get_task since targeted by REX [http://reverse.put.as/2014/03/18/teaching-rex-another-trustedbsd-trick-to-hide-from-volatility/] func_addr = self.addr_space.profile.get_symbol('_mac_proc_check_get_task') content = self.addr_space.read(func_addr, 1024) op_prev = None for op in distorm3.Decompose(func_addr, content, distorm_mode): if not op.valid or (op.mnemonic == 'NOP' and op_prev.mnemonic == "RET"): break if model == "64bit": if op.mnemonic == 'LEA' and op.operands[0].type == 'Register' and op.operands[0].name in ['RDI','RAX','R13','RSP','RBX','R12','R13','R14','R15']: curr_mpl_addr = op.address + op.operands[1].disp + op.size curr_mpl = obj.Object("mac_policy_list", offset = curr_mpl_addr, vm = self.addr_space) # check if mac_policy_list address and mac_policy_list.entries address have changed if curr_mpl_addr != original_mpl_addr or original_mpl.entries.v() != curr_mpl.entries.v(): is_shadowed = True shadow_addr = curr_mpl_addr yield(original_mpl_addr, shadow_addr, op.address) print "mac_policy_address is shadowed! Original Address: {0:#10x}, Shadow Address: {1:#10x}, Modification at: {2:#10x}".format(original_mpl_addr, shadow_addr, op.address) break elif model == "32bit": if op.mnemonic == 'MOV' and op.operands[0].type == 'Register' and op.operands[0].name in ['EAX'] and op.operands[1].type == 'AbsoluteMemoryAddress': curr_mpl_entries_addr = op.operands[1].disp # check if mac_policy_list.entries address has changed if curr_mpl_entries_addr != original_mpl.entries.v(): is_shadowed = True shadow_addr = curr_mpl_entries_addr yield (original_mpl.entries.v(), shadow_addr, op.address) print "mac_policy_address is shadowed! Original Entries Address: {0:#10x}, Shadow Entries Address: {1:#10x}, Modification at: {2:#10x}".format(original_mpl.entries.v(), shadow_addr, op.address) break op_prev = op
def calculate(self): args = () common.set_plugin_members(self) sym_addrs = self.profile.get_all_addresses() nsysent = obj.Object( "int", offset=self.addr_space.profile.get_symbol("_nsysent"), vm=self.addr_space) sysents = obj.Object( theType="Array", offset=self.addr_space.profile.get_symbol("_sysent"), vm=self.addr_space, count=nsysent, targetType="sysent") for (i, sysent) in enumerate(sysents): hooked = False ent_addr = sysent.sy_call.v() syscall_name = self.profile.get_symbol_by_address( "kernel", ent_addr) if 'dtrace' in syscall_name: # syscall probes: http://siliconblade.blogspot.com/2013/04/hunting-d-trace-rootkits-with.html hooked = "syscall_probe" else: # fbt probes: http://reverse.put.as/2013/05/07/syscan13-revisiting-mac-os-x-rootkits-presentation/ data = sysent.obj_vm.zread(ent_addr + 1, 2) if binascii.hexlify(data) == 'f089': hooked = 'fbt_probe' if hooked != False: yield ('Syscall_Table', i, ent_addr, syscall_name, hooked) # mach_trap probes: http://felinemenace.org/~nemo/dtrace-infiltrate.pdf t = trap.mac_check_trap_table(self._config, args) for (table_addr, table_name, index, call_addr, sym_name, other_hooked) in t.calculate(): if other_hooked == True: sym_name = "HOOKED" if 'dtrace' in sym_name: hooked = "mach_trap_probe" yield ('Trap_Table', index, call_addr, sym_name, hooked)
def calculate(self): common.set_plugin_members(self) sym_addrs = self.profile.get_all_addresses() table_addr = self.addr_space.profile.get_symbol("_sysent") nsysent = obj.Object("int", offset = self.addr_space.profile.get_symbol("_nsysent"), vm = self.addr_space) sysents = obj.Object(theType = "Array", offset = table_addr, vm = self.addr_space, count = nsysent, targetType = "sysent") for (i, sysent) in enumerate(sysents): ent_addr = sysent.sy_call.v() hooked = ent_addr not in sym_addrs if hooked == False: sym_name = self.profile.get_symbol_by_address("kernel", ent_addr) else: sym_name = "HOOKED" yield (table_addr, "SyscallTable", i, ent_addr, hooked, sym_name)
def calculate(self): common.set_plugin_members(self) (kernel_symbol_addresses, kmod) = common.get_kernel_function_addrs(self) model = self.addr_space.profile.metadata.get('memory_model', 0) if model == '32bit': distorm_mode = distorm3.Decode32Bits else: distorm_mode = distorm3.Decode64Bits sym_addrs = self.profile.get_all_function_addresses() # get kernel start, end kp = self.addr_space.profile.get_symbol("_g_kernel_kmod_info") kmodk = obj.Object("kmod_info", offset = kp, vm = self.addr_space) k_start = kmodk.address k_end = k_start + kmodk.m('size') ####### STEP 1 - CHECK SYSTEM CALL INLINE HOOKS ############ # get syscall table nsysent = obj.Object("int", offset = self.addr_space.profile.get_symbol("_nsysent"), vm = self.addr_space) sysents = obj.Object(theType = "Array", offset = self.addr_space.profile.get_symbol("_sysent"), vm = self.addr_space, count = nsysent, targetType = "sysent") # check if syscall table entries have been modified dict_syscall_funcs = {} list_syscall_names = [] for (i, sysent) in enumerate(sysents): ent_addr = sysent.sy_call.v() hooked = ent_addr not in sym_addrs # using check_syscalls method inlined, dst_addr = self.isInlined(model, distorm_mode, ent_addr, kernel_symbol_addresses, [kmodk]) prolog_inlined = self.isPrologInlined(model, distorm_mode, ent_addr) if hooked == True or inlined == True or prolog_inlined == True: if dst_addr != None: kext = self.findKextWithAddress(dst_addr) else: kext = self.findKextWithAddress(ent_addr) yield ("SyscallTable1", i, ent_addr, hooked, (inlined or prolog_inlined), False, '-', kext) else: ent_name = self.profile.get_symbol_by_address_type("kernel", ent_addr, "N_FUN") # check for duplicate syscall functions if ent_name != "_nosys" and ent_name in dict_syscall_funcs: prev_ent = dict_syscall_funcs[ent_name] kext = self.findKextWithAddress(ent_addr) yield ("SyscallTable", list_syscall_names.index(ent_name), prev_ent.sy_call.v(), False, False, False, '-', kext) yield ("DuplicateSyscall -> {0}".format(ent_name), i, ent_addr, True, False, False, '-', kext) else: # check for dtrace syscall hooks if ent_name.find("dtrace") > -1: kext = self.findKextWithAddress(ent_addr) yield ("SyscallTable", i, ent_addr, False, False, False, '-', kext) else: # add to list list_syscall_names.append(ent_name) dict_syscall_funcs[ent_name] = sysent ####### STEP 2 - KERNEL & KEXTS ############### # get symbols from kext __TEXT in memory rather than file kext_addr_list = [] # get kernel address kmod = obj.Object("kmod_info", offset = self.addr_space.profile.get_symbol("_g_kernel_kmod_info"), vm = self.addr_space) kext_addr_list.append((kmod.address.v(), kmod.address + kmod.m('size'), '__kernel__')) # get other kext addresses p = self.addr_space.profile.get_symbol("_kmod") kmodaddr = obj.Object("Pointer", offset = p, vm = self.addr_space) kmod = kmodaddr.dereference_as("kmod_info") while kmod.is_valid(): kext_addr_list.append((kmod.address.v(), kmod.address + kmod.m('size'), kmod.name)) kmod = kmod.next # loop thru kexts for kext_address, kext_end, kext_name in kext_addr_list: #loop thru kext functions for func_name, func_addr in self.getKextSymbols(kext_addr = kext_address, onlyFunctions = True, fmodel = model): inlined = False # false positive, remove if needed if func_name in ["pthreads_dummy_symbol"]: continue # check if function's been modified modified, dst_addr = self.isCallReferenceModified(model, distorm_mode, func_addr, kernel_symbol_addresses, kext_addr_list) if modified: if dst_addr != None: hook_kext = self.findKextWithAddress(dst_addr) else: hook_kext = kext_name yield ("SymbolsTable", '-', func_addr, False, modified, False, '-', hook_kext) inlined, dst_addr = self.isInlined(model, distorm_mode, func_addr, kernel_symbol_addresses, kext_addr_list) if inlined: if dst_addr != None: hook_kext = self.findKextWithAddress(dst_addr) else: hook_kext = kext_name yield ("SymbolsTable", '-', func_addr, False, inlined, False, '-', hook_kext) ########## STEP 3 - TRAP TABLE ############### # check if trap table hooked using check_trap_table args = () trap = check_trap_table.mac_check_trap_table(self._config, args) for (table_addr, table_name, i, call_addr, sym_name, hooked) in trap.calculate(): if hooked == True or 'dtrace' in sym_name: kext = self.findKextWithAddress(call_addr) yield ("TrapTable", i, call_addr, hooked, False, False, '-', kext) else: inlined, dst_addr = self.isInlined(model, distorm_mode, call_addr, kernel_symbol_addresses, [kmodk]) if inlined: if dst_addr != None: hook_kext = self.findKextWithAddress(dst_addr) else: hook_kext = kext_name yield ("TrapTable", '-', func_addr, False, inlined, False, '-', hook_kext) else: modified, dst_addr = self.isCallReferenceModified(model, distorm_mode, call_addr, kernel_symbol_addresses, [kmodk]) if modified: if dst_addr != None: hook_kext = self.findKextWithAddress(dst_addr) else: hook_kext = kext_name yield ("TrapTable", '-', func_addr, False, modified, False, '-', hook_kext)
def calculate(self): common.set_plugin_members(self) args = () memory_model = self.addr_space.profile.metadata.get('memory_model', 0) if memory_model == '32bit': distorm_mode = distorm3.Decode32Bits master_idt_type = "_master_idt64" idt_type = "real_gate64" else: idt_type = "real_gate64" distorm_mode = distorm3.Decode64Bits master_idt_type = "_master_idt64" sym_addrs = self.profile.get_all_addresses() # size of idt table is 256 so far IDTSZ = 256 # get number of cpus real_ncpus = obj.Object("int", offset = self.addr_space.profile.get_symbol("_real_ncpus"), vm = self.addr_space) # get cpu_data_ptr/cpu_data array addresses cpu_data_ptrs = obj.Object(theType = 'Array', offset = self.addr_space.profile.get_symbol("_cpu_data_ptr"), vm = self.addr_space, targetType = "unsigned long long", count = real_ncpus) # master_idt not in cpu_desc_table so use the symbol instead: http://www.opensource.apple.com/source/xnu/xnu-1699.24.8/osfmk/i386/mp_desc.c master_idt_addr = self.addr_space.profile.get_symbol(master_idt_type) for i in range(0, real_ncpus -1): # for master cpu, use master_idt_addr if i == 0: idt_addr = master_idt_addr else: if memory_model == "32bit": break cpu_data = obj.Object('cpu_data', offset = cpu_data_ptrs[i], vm = self.addr_space) # get idt addr which is at the beginning of the cpu_desc_table idt_addr = cpu_data.cpu_desc_tablep.v() idt_table = obj.Object(theType = 'Array', offset = idt_addr, vm = self.addr_space, targetType = idt_type, count = IDTSZ) for j in range(0, IDTSZ): hooked = False inlined = True ent = idt_table[j] if memory_model == '32bit': idt32_nums = [0,1,255] + range(3,32) + range(127, 132) if j not in idt32_nums: continue stub_addr = ent.offset_low16 + (ent.offset_high16 << 16) else: stub_addr = ent.offset_low16 + (ent.offset_high16 << 16) + (ent.offset_top32 << 32) selector = dict_seg64.get(int(ent.selector16), 'UNKNOWN') # 6th and 7th bits are for dpl # http://www.logix.cz/michal/doc/i386/chp06-03.htm dpl = (ent.access8 >> 5) & 0x3 if stub_addr in sym_addrs: idt_name = self.profile.get_symbol_by_address("kernel", stub_addr) # symbols file has same address for both _hi64_text_base and _t64_zero_div, proper name is _t64_zero_div if memory_model == "32bit" and idt_name == "_hi64_text_base": idt_name = "_t64_zero_div" else: idt_name = "UNKNOWN" module_name = self.findKextWithAddress(stub_addr) # check if idt stub is within the kernel OR function/idt name is somewhat known idt entry if str(module_name) == "__kernel__" and idt_name[:4] in ['_idt', '__in', '_t64', '_hi6', '_a64', '_mc6'] and idt_name != "UNKNOWN": hooked = False else: hooked = True # check if handler has been tampered with if self.hasValidHandler(stub_addr, memory_model, distorm_mode): inlined = False # uncomment the next line and comment the following to use the module in the script as described at: http://siliconblade.blogspot.com/ # yield(i, j, stub_addr, idt_name, dpl, selector, module_name, hooked, inlined, ent) yield(i, j, stub_addr, idt_name, dpl, selector, module_name, hooked, inlined)
def calculate(self): common.set_plugin_members(self) (kernel_symbol_addresses, kmod) = common.get_kernel_function_addrs(self) model = self.addr_space.profile.metadata.get('memory_model', 0) if model == '32bit': distorm_mode = distorm3.Decode32Bits else: distorm_mode = distorm3.Decode64Bits sym_addrs = self.profile.get_all_function_addresses() # get kernel start, end kp = self.addr_space.profile.get_symbol("_g_kernel_kmod_info") kmodk = obj.Object("kmod_info", offset=kp, vm=self.addr_space) k_start = kmodk.address k_end = k_start + kmodk.m('size') ####### STEP 1 - CHECK SYSTEM CALL INLINE HOOKS ############ # get syscall table nsysent = obj.Object( "int", offset=self.addr_space.profile.get_symbol("_nsysent"), vm=self.addr_space) sysents = obj.Object( theType="Array", offset=self.addr_space.profile.get_symbol("_sysent"), vm=self.addr_space, count=nsysent, targetType="sysent") # check if syscall table entries have been modified dict_syscall_funcs = {} list_syscall_names = [] for (i, sysent) in enumerate(sysents): ent_addr = sysent.sy_call.v() hooked = ent_addr not in sym_addrs # using check_syscalls method inlined, dst_addr = self.isInlined(model, distorm_mode, ent_addr, kernel_symbol_addresses, [kmodk]) prolog_inlined = self.isPrologInlined(model, distorm_mode, ent_addr) if hooked == True or inlined == True or prolog_inlined == True: if dst_addr != None: kext = self.findKextWithAddress(dst_addr) else: kext = self.findKextWithAddress(ent_addr) yield ("SyscallTable1", i, ent_addr, hooked, (inlined or prolog_inlined), False, '-', kext) else: ent_name = self.profile.get_symbol_by_address_type( "kernel", ent_addr, "N_FUN") # check for duplicate syscall functions if ent_name != "_nosys" and ent_name in dict_syscall_funcs: prev_ent = dict_syscall_funcs[ent_name] kext = self.findKextWithAddress(ent_addr) yield ("SyscallTable", list_syscall_names.index(ent_name), prev_ent.sy_call.v(), False, False, False, '-', kext) yield ("DuplicateSyscall -> {0}".format(ent_name), i, ent_addr, True, False, False, '-', kext) else: # check for dtrace syscall hooks if ent_name.find("dtrace") > -1: kext = self.findKextWithAddress(ent_addr) yield ("SyscallTable", i, ent_addr, False, False, False, '-', kext) else: # add to list list_syscall_names.append(ent_name) dict_syscall_funcs[ent_name] = sysent ####### STEP 2 - KERNEL & KEXTS ############### # get symbols from kext __TEXT in memory rather than file kext_addr_list = [] # get kernel address kmod = obj.Object( "kmod_info", offset=self.addr_space.profile.get_symbol("_g_kernel_kmod_info"), vm=self.addr_space) kext_addr_list.append( (kmod.address.v(), kmod.address + kmod.m('size'), '__kernel__')) # get other kext addresses p = self.addr_space.profile.get_symbol("_kmod") kmodaddr = obj.Object("Pointer", offset=p, vm=self.addr_space) kmod = kmodaddr.dereference_as("kmod_info") while kmod.is_valid(): kext_addr_list.append( (kmod.address.v(), kmod.address + kmod.m('size'), kmod.name)) kmod = kmod.next # loop thru kexts for kext_address, kext_end, kext_name in kext_addr_list: #loop thru kext functions for func_name, func_addr in self.getKextSymbols( kext_addr=kext_address, onlyFunctions=True, fmodel=model): inlined = False # false positive, remove if needed if func_name in ["pthreads_dummy_symbol"]: continue # check if function's been modified modified, dst_addr = self.isCallReferenceModified( model, distorm_mode, func_addr, kernel_symbol_addresses, kext_addr_list) if modified: if dst_addr != None: hook_kext = self.findKextWithAddress(dst_addr) else: hook_kext = kext_name yield ("SymbolsTable", '-', func_addr, False, modified, False, '-', hook_kext) inlined, dst_addr = self.isInlined(model, distorm_mode, func_addr, kernel_symbol_addresses, kext_addr_list) if inlined: if dst_addr != None: hook_kext = self.findKextWithAddress(dst_addr) else: hook_kext = kext_name yield ("SymbolsTable", '-', func_addr, False, inlined, False, '-', hook_kext) ########## STEP 3 - TRAP TABLE ############### # check if trap table hooked using check_trap_table args = () trap = check_trap_table.mac_check_trap_table(self._config, args) for (table_addr, table_name, i, call_addr, sym_name, hooked) in trap.calculate(): if hooked == True or 'dtrace' in sym_name: kext = self.findKextWithAddress(call_addr) yield ("TrapTable", i, call_addr, hooked, False, False, '-', kext) else: inlined, dst_addr = self.isInlined(model, distorm_mode, call_addr, kernel_symbol_addresses, [kmodk]) if inlined: if dst_addr != None: hook_kext = self.findKextWithAddress(dst_addr) else: hook_kext = kext_name yield ("TrapTable", '-', func_addr, False, inlined, False, '-', hook_kext) else: modified, dst_addr = self.isCallReferenceModified( model, distorm_mode, call_addr, kernel_symbol_addresses, [kmodk]) if modified: if dst_addr != None: hook_kext = self.findKextWithAddress(dst_addr) else: hook_kext = kext_name yield ("TrapTable", '-', func_addr, False, modified, False, '-', hook_kext)
def calculate(self): common.set_plugin_members(self) model = self.addr_space.profile.metadata.get('memory_model', 0) if model == '32bit': distorm_mode = distorm3.Decode32Bits else: distorm_mode = distorm3.Decode64Bits sym_addrs = self.profile.get_all_addresses() # get all kexts and symbols if self._config.DISPLAYSYMBOLS != False: kext_addr_list = [] # get kernel address kmod = obj.Object("kmod_info", offset = self.addr_space.profile.get_symbol("_g_kernel_kmod_info"), vm = self.addr_space) kext_addr_list.append(('__kernel__', kmod.address, kmod.m('size'))) p = self.addr_space.profile.get_symbol("_kmod") kmodaddr = obj.Object("Pointer", offset = p, vm = self.addr_space) kmod = kmodaddr.dereference_as("kmod_info") while kmod.is_valid(): kext_addr_list.append((kmod.name, kmod.address, kmod.m('size'))) kmod = kmod.next # loop thru kexts for kext_name, kext_address, kext_size in kext_addr_list: if self._config.DISPLAYSYMBOLS in "{0}".format(kext_name): k_start = kext_address k_end = kext_address + kext_size #loop thru kext functions for func_name, func_addr in self.getKextSymbols(kext_addr = kext_address, onlyFunctions = True, fmodel = model): print "{0} {1} {2:#10x}".format(kext_name, func_name, func_addr) yield ('-', 0, False, False, False, False, '-', '-') return # get kernel start, end kp = self.addr_space.profile.get_symbol("_g_kernel_kmod_info") kmodk = obj.Object("kmod_info", offset = kp, vm = self.addr_space) k_start = kmodk.address k_end = k_start + kmodk.m('size') # get syscall table nsysent = obj.Object("int", offset = self.addr_space.profile.get_symbol("_nsysent"), vm = self.addr_space) sysents = obj.Object(theType = "Array", offset = self.addr_space.profile.get_symbol("_sysent"), vm = self.addr_space, count = nsysent, targetType = "sysent") # check if syscall table is shadowed, if so use shadow address (syscall_shadowed, shadow_addr) = self.isSyscallShadowed(model, distorm_mode, sysents.obj_offset) if syscall_shadowed: # use shadow table address to check hooking sysents = obj.Object(theType = "Array", offset = shadow_addr, vm = self.addr_space, count = nsysent, targetType = "sysent") # check if shadow syscall functions have been modified for (i, sysent) in enumerate(sysents): ent_addr = sysent.sy_call.v() inlined, dst_addr = self.isInlined(model, distorm_mode, ent_addr, k_start, k_end) hooked = ent_addr not in sym_addrs # using check_syscalls method prolog_inlined = self.isPrologInlined(model, distorm_mode, ent_addr) if hooked == True or inlined == True or prolog_inlined == True: if dst_addr != None: kext = self.findKextWithAddress(dst_addr) else: kext = self.findKextWithAddress(ent_addr) yield ("ShadowSyscallTable", i, ent_addr, hooked, inlined, syscall_shadowed, '-', kext) else: # check if syscall table entries have been modified dict_syscall_funcs = {} list_syscall_names = [] for (i, sysent) in enumerate(sysents): ent_addr = sysent.sy_call.v() hooked = ent_addr not in sym_addrs # using check_syscalls method inlined, dst_addr = self.isInlined(model, distorm_mode, ent_addr, k_start, k_end) prolog_inlined = self.isPrologInlined(model, distorm_mode, ent_addr) if hooked == True or inlined == True or prolog_inlined == True: if dst_addr != None: kext = self.findKextWithAddress(dst_addr) else: kext = self.findKextWithAddress(ent_addr) yield ("SyscallTable", i, ent_addr, hooked, (inlined or prolog_inlined), False, '-', kext) else: ent_name = self.profile.get_symbol_by_address("kernel", ent_addr) # check for duplicate syscall functions if ent_name != "_nosys" and ent_name in dict_syscall_funcs: prev_ent = dict_syscall_funcs[ent_name] kext = self.findKextWithAddress(ent_addr) yield ("SyscallTable", list_syscall_names.index(ent_name), prev_ent.sy_call.v(), False, False, False, '-', kext) yield ("DuplicateSyscall -> {0}".format(ent_name), i, ent_addr, True, False, False, '-', kext) else: # check for dtrace syscall hooks if ent_name.find("dtrace") > -1: kext = self.findKextWithAddress(ent_addr) yield ("SyscallTable", i, ent_addr, False, False, False, '-', kext) else: # add to list list_syscall_names.append(ent_name) dict_syscall_funcs[ent_name] = sysent # check if kext functions are inlined, including kernel symbols if self._config.CHECKKEXTS or self._config.CHECKKERNEL: # get symbols from kext __TEXT in memory rather than file kext_addr_list = [] # get kernel address kmod = obj.Object("kmod_info", offset = self.addr_space.profile.get_symbol("_g_kernel_kmod_info"), vm = self.addr_space) kext_addr_list.append(('__kernel__', kmod.address, kmod.m('size'))) # get all kexts if self._config.CHECKKERNEL == False: # get other kext addresses p = self.addr_space.profile.get_symbol("_kmod") kmodaddr = obj.Object("Pointer", offset = p, vm = self.addr_space) kmod = kmodaddr.dereference_as("kmod_info") while kmod.is_valid(): kext_addr_list.append((kmod.name, kmod.address, kmod.m('size'))) kmod = kmod.next # loop thru kexts for kext_name, kext_address, kext_size in kext_addr_list: k_start = kext_address k_end = kext_address + kext_size #loop thru kext functions for func_name, func_addr in self.getKextSymbols(kext_addr = kext_address, onlyFunctions = True, fmodel = model): inlined = False # false positive, remove if needed if func_name in ["pthreads_dummy_symbol"]: continue # check if function's been modified modified, dst_addr = self.isCallReferenceModified(model, distorm_mode, func_addr, k_start, k_end) if modified: if dst_addr != None: hook_kext = self.findKextWithAddress(dst_addr) else: hook_kext = kext_name yield ("SymbolsTable", '-', func_addr, False, modified, False, '-', hook_kext) inlined, dst_addr = self.isInlined(model, distorm_mode, func_addr, k_start, k_end) #prolog_inlined = self.isPrologInlined(model, distorm_mode, func_addr) if inlined: if dst_addr != None: hook_kext = self.findKextWithAddress(dst_addr) else: hook_kext = kext_name yield ("SymbolsTable", '-', func_addr, False, (inlined or prolog_inlined), False, '-', hook_kext) # check if trap table hooked using check_trap_table args = () trap = check_trap_table.mac_check_trap_table(self._config, args) for (table_addr, table_name, i, call_addr, sym_name, hooked) in trap.calculate(): if hooked == True or 'dtrace' in sym_name: kext = self.findKextWithAddress(call_addr) yield ("TrapTable", i, call_addr, hooked, False, False, '-', kext)
def calculate(self): common.set_plugin_members(self) model = self.addr_space.profile.metadata.get('memory_model', 0) if model == '32bit': distorm_mode = distorm3.Decode32Bits else: distorm_mode = distorm3.Decode64Bits sym_addrs = self.profile.get_all_addresses() # get all kexts and symbols if self._config.DISPLAYSYMBOLS != False: kext_addr_list = [] # get kernel address kmod = obj.Object("kmod_info", offset=self.addr_space.profile.get_symbol( "_g_kernel_kmod_info"), vm=self.addr_space) kext_addr_list.append(('__kernel__', kmod.address, kmod.m('size'))) p = self.addr_space.profile.get_symbol("_kmod") kmodaddr = obj.Object("Pointer", offset=p, vm=self.addr_space) kmod = kmodaddr.dereference_as("kmod_info") while kmod.is_valid(): kext_addr_list.append( (kmod.name, kmod.address, kmod.m('size'))) kmod = kmod.next # loop thru kexts for kext_name, kext_address, kext_size in kext_addr_list: if self._config.DISPLAYSYMBOLS in "{0}".format(kext_name): k_start = kext_address k_end = kext_address + kext_size #loop thru kext functions for func_name, func_addr in self.getKextSymbols( kext_addr=kext_address, onlyFunctions=True, fmodel=model): print "{0} {1} {2:#10x}".format( kext_name, func_name, func_addr) yield ('-', 0, False, False, False, False, '-', '-') return # get kernel start, end kp = self.addr_space.profile.get_symbol("_g_kernel_kmod_info") kmodk = obj.Object("kmod_info", offset=kp, vm=self.addr_space) k_start = kmodk.address k_end = k_start + kmodk.m('size') # get syscall table nsysent = obj.Object( "int", offset=self.addr_space.profile.get_symbol("_nsysent"), vm=self.addr_space) sysents = obj.Object( theType="Array", offset=self.addr_space.profile.get_symbol("_sysent"), vm=self.addr_space, count=nsysent, targetType="sysent") # check if syscall table is shadowed, if so use shadow address (syscall_shadowed, shadow_addr) = self.isSyscallShadowed(model, distorm_mode, sysents.obj_offset) if syscall_shadowed: # use shadow table address to check hooking sysents = obj.Object(theType="Array", offset=shadow_addr, vm=self.addr_space, count=nsysent, targetType="sysent") # check if shadow syscall functions have been modified for (i, sysent) in enumerate(sysents): ent_addr = sysent.sy_call.v() inlined, dst_addr = self.isInlined(model, distorm_mode, ent_addr, k_start, k_end) hooked = ent_addr not in sym_addrs # using check_syscalls method prolog_inlined = self.isPrologInlined(model, distorm_mode, ent_addr) if hooked == True or inlined == True or prolog_inlined == True: if dst_addr != None: kext = self.findKextWithAddress(dst_addr) else: kext = self.findKextWithAddress(ent_addr) yield ("ShadowSyscallTable", i, ent_addr, hooked, inlined, syscall_shadowed, '-', kext) else: # check if syscall table entries have been modified dict_syscall_funcs = {} list_syscall_names = [] for (i, sysent) in enumerate(sysents): ent_addr = sysent.sy_call.v() hooked = ent_addr not in sym_addrs # using check_syscalls method inlined, dst_addr = self.isInlined(model, distorm_mode, ent_addr, k_start, k_end) prolog_inlined = self.isPrologInlined(model, distorm_mode, ent_addr) if hooked == True or inlined == True or prolog_inlined == True: if dst_addr != None: kext = self.findKextWithAddress(dst_addr) else: kext = self.findKextWithAddress(ent_addr) yield ("SyscallTable", i, ent_addr, hooked, (inlined or prolog_inlined), False, '-', kext) else: ent_name = self.profile.get_symbol_by_address( "kernel", ent_addr) # check for duplicate syscall functions if ent_name != "_nosys" and ent_name in dict_syscall_funcs: prev_ent = dict_syscall_funcs[ent_name] kext = self.findKextWithAddress(ent_addr) yield ("SyscallTable", list_syscall_names.index(ent_name), prev_ent.sy_call.v(), False, False, False, '-', kext) yield ("DuplicateSyscall -> {0}".format(ent_name), i, ent_addr, True, False, False, '-', kext) else: # check for dtrace syscall hooks if ent_name.find("dtrace") > -1: kext = self.findKextWithAddress(ent_addr) yield ("SyscallTable", i, ent_addr, False, False, False, '-', kext) else: # add to list list_syscall_names.append(ent_name) dict_syscall_funcs[ent_name] = sysent # check if kext functions are inlined, including kernel symbols if self._config.CHECKKEXTS or self._config.CHECKKERNEL: # get symbols from kext __TEXT in memory rather than file kext_addr_list = [] # get kernel address kmod = obj.Object("kmod_info", offset=self.addr_space.profile.get_symbol( "_g_kernel_kmod_info"), vm=self.addr_space) kext_addr_list.append(('__kernel__', kmod.address, kmod.m('size'))) # get all kexts if self._config.CHECKKERNEL == False: # get other kext addresses p = self.addr_space.profile.get_symbol("_kmod") kmodaddr = obj.Object("Pointer", offset=p, vm=self.addr_space) kmod = kmodaddr.dereference_as("kmod_info") while kmod.is_valid(): kext_addr_list.append( (kmod.name, kmod.address, kmod.m('size'))) kmod = kmod.next # loop thru kexts for kext_name, kext_address, kext_size in kext_addr_list: k_start = kext_address k_end = kext_address + kext_size #loop thru kext functions for func_name, func_addr in self.getKextSymbols( kext_addr=kext_address, onlyFunctions=True, fmodel=model): inlined = False # false positive, remove if needed if func_name in ["pthreads_dummy_symbol"]: continue # check if function's been modified modified, dst_addr = self.isCallReferenceModified( model, distorm_mode, func_addr, k_start, k_end) if modified: if dst_addr != None: hook_kext = self.findKextWithAddress(dst_addr) else: hook_kext = kext_name yield ("SymbolsTable", '-', func_addr, False, modified, False, '-', hook_kext) inlined, dst_addr = self.isInlined(model, distorm_mode, func_addr, k_start, k_end) #prolog_inlined = self.isPrologInlined(model, distorm_mode, func_addr) if inlined: if dst_addr != None: hook_kext = self.findKextWithAddress(dst_addr) else: hook_kext = kext_name yield ("SymbolsTable", '-', func_addr, False, (inlined or prolog_inlined), False, '-', hook_kext) # check if trap table hooked using check_trap_table args = () trap = check_trap_table.mac_check_trap_table(self._config, args) for (table_addr, table_name, i, call_addr, sym_name, hooked) in trap.calculate(): if hooked == True or 'dtrace' in sym_name: kext = self.findKextWithAddress(call_addr) yield ("TrapTable", i, call_addr, hooked, False, False, '-', kext)